diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..637a0a4f --- /dev/null +++ b/build.gradle @@ -0,0 +1,178 @@ +buildscript { + repositories { + maven { url 'http://repo.springsource.org/libs-release'} + maven { url 'http://repo.springsource.org/plugins-release' } + } + dependencies { + classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") + classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.2') + classpath("io.spring.gradle:docbook-reference-plugin:0.3.0") + } +} + +configure(allprojects) { + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'idea' + + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + + group = 'org.springframework.statemachine' + + [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none'] + + repositories { + mavenCentral() + maven { url "http://repo.springsource.org/libs-release" } + } + + task integrationTest(type: Test) { + include '**/*IntegrationTests.*' + } + + test { + exclude '**/*IntegrationTests.*' + } + + // servlet-api (2.5) and tomcat-servlet-api (3.0) classpath entries should not be + // exported to dependent projects in Eclipse to avoid false compilation errors due + // to changing APIs across these versions + eclipse.classpath.file.whenMerged { classpath -> + classpath.entries.findAll { entry -> entry.path.contains('servlet-api') }*.exported = false + } +} + +configure(subprojects) { subproject -> + apply from: "${rootProject.projectDir}/publish-maven.gradle" + + 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 + } +} + +project('spring-statemachine-core') { + description = "Spring State Machine Core" + + dependencies { + compile "org.springframework:spring-messaging:$springVersion" + + testCompile "org.springframework:spring-test:$springVersion" + testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" + testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" + testCompile "junit:junit:$junitVersion" + } +} + +configure(rootProject) { + description = 'Spring State Machine' + + apply plugin: 'org.asciidoctor.gradle.asciidoctor' + apply plugin: "docbook-reference" + + // don't publish the default jar for the root project + configurations.archives.artifacts.clear() + + reference { + sourceDir = new File(asciidoctor.outputDir , 'docbook5') + pdfFilename = "spring-statemachine-reference.pdf" + epubFilename = "spring-statemachine-reference.epub" + expandPlaceholders = "" + } + + afterEvaluate { + tasks.findAll { it.name.startsWith("reference") }.each{ it.dependsOn.add("asciidoctor") } + } + + asciidoctorj { + version = '1.5.2' + } + + asciidoctor { + sourceDir = file("docs/src/reference/asciidoc") + backends = ['docbook5'] + options eruby: 'erubis' + attributes docinfo: '', + copycss : '', + icons : 'font', + 'source-highlighter': 'prettify', + sectanchors : '', + toc2: '', + idprefix: '', + idseparator: '-', + doctype: 'book', + numbered: '', + 'spring-hadoop-version' : project.version, + 'spring-version' : springVersion, + revnumber : project.version + } + + dependencies { // for integration tests + } + + 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.overview = 'src/api/overview.html' + options.links( + 'http://docs.jboss.org/jbossas/javadoc/4.0.5/connector' + ) + source subprojects.collect { project -> + project.sourceSets.main.allJava + } + destinationDir = new File(buildDir, "api") + classpath = files(subprojects.collect { project -> + project.sourceSets.main.compileClasspath + }) + maxMemory = '1024m' + } + + task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '2.2.1' + } + +} + diff --git a/docs/src/api/overview.html b/docs/src/api/overview.html new file mode 100644 index 00000000..5152c299 --- /dev/null +++ b/docs/src/api/overview.html @@ -0,0 +1,14 @@ + + +This document is the API specification for the Spring Statemachine project. +
+ +
+

+ For further API reference and developer documentation, see the + Spring Statemachine Project Page. + There you can find the latest news, links to documentation, books, presentations and webinars. +

+
+ + diff --git a/docs/src/api/stylesheet.css b/docs/src/api/stylesheet.css new file mode 100644 index 00000000..029c89c9 --- /dev/null +++ b/docs/src/api/stylesheet.css @@ -0,0 +1,599 @@ +/* Javadoc style sheet */ +/* +Overall document style +*/ + +@import url('resources/fonts/dejavu.css'); + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a:hover, a:focus { + text-decoration:none; + color:#bb7a2a; +} +a:active { + text-decoration:none; + color:#4A6782; +} +a[name] { + color:#353833; +} +a[name]:hover { + text-decoration:none; + color:#353833; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; + font-style:italic; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} +/* +Document title and Copyright styles +*/ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* +Navigation bar styles +*/ +.bar { + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; +} +.topNav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottomNav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.subNav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +ul.subNavList li{ + list-style:none; + float:left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.navBarCell1Rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexHeader { + margin:10px; + position:relative; +} +.indexHeader span{ + margin-right:15px; +} +.indexHeader h1 { + font-size:13px; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:12px; +} +.indexContainer h2 { + font-size:13px; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; + padding-top:2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Sans Mono',monospace; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* +List styles +*/ +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* +Table styles +*/ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { + width:100%; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overviewSummary, .memberSummary { + padding:0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} +.memberSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; + +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { + text-align:left; + padding:0px 0px 12px 10px; + width:100%; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colLast, th.colOne, .constantsSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + white-space:nowrap; + font-size:13px; +} +td.colLast, th.colLast { + font-size:13px; +} +td.colOne, th.colOne { + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width:25%; + vertical-align:top; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; +} +.tableSubHeadingColor { + background-color:#EEEEFF; +} +.altColor { + background-color:#FFFFFF; +} +.rowColor { + background-color:#EEEEEF; +} +/* +Content styles +*/ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} + +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} + +td.colLast div { + padding-top:0px; +} + + +td.colLast a { + padding-bottom:3px; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:3px 10px 2px 0px; + color:#474747; +} +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { + font-weight:bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom:0px; +} + + + +/* +Spring +*/ + +pre.code { + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + overflow: auto; + padding: 10px; + margin: 4px 20px 2px 0px; +} + +pre.code code, pre.code code * { + font-size: 1em; +} + +pre.code code, pre.code code * { + padding: 0 !important; + margin: 0 !important; +} + diff --git a/docs/src/info/changelog.txt b/docs/src/info/changelog.txt new file mode 100644 index 00000000..2621608c --- /dev/null +++ b/docs/src/info/changelog.txt @@ -0,0 +1,4 @@ +SPRING STATEMACHINE CHANGELOG +================================== +http://projects.spring.io/spring-statemachine/ + diff --git a/docs/src/info/license.txt b/docs/src/info/license.txt new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/docs/src/info/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/docs/src/info/notice.txt b/docs/src/info/notice.txt new file mode 100644 index 00000000..06973028 --- /dev/null +++ b/docs/src/info/notice.txt @@ -0,0 +1,21 @@ +====================================================================== +== NOTICE file corresponding to section 4 d of the Apache License, == +== Version 2.0, for the Spring Framework distribution. == +====================================================================== + +This product includes software developed by +the Apache Software Foundation (http://www.apache.org). + +The end-user documentation included with a redistribution, if any, +must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + +Alternately, this acknowledgement may appear in the software itself, +if and wherever such third-party acknowledgements normally appear. + +The names "Spring", "Spring Framework" and "Spring for Apache Hadoop" +must not be used to endorse or promote products derived from this +software without prior written permission. For written permission, +please contact enquiries@springsource.com. diff --git a/docs/src/info/readme.txt b/docs/src/info/readme.txt new file mode 100644 index 00000000..4eb0c911 --- /dev/null +++ b/docs/src/info/readme.txt @@ -0,0 +1,24 @@ +SPRING STATEMACHINE +------------------------ +http://projects.spring.io/spring-statemachine/ + +1. INTRODUCTION + +Spring Statemachine is a framework extension introducing state machine +concepts in a spring world. + +2. RELEASE NOTES + +This release comes with complete reference documentation. For further +details, consult the provided javadoc for specific packages and classes. + +3. DISTRIBUTION JAR FILES + +The Spring Statemachine jars files can be found in the 'dist' directory. + +4. GETTING STARTED + +Please see the reference documentation. +Additionally the blog at http://blog.spring.io as well +as sections of interest in the reference documentation. + diff --git a/docs/src/reference/asciidoc/Guardfile b/docs/src/reference/asciidoc/Guardfile new file mode 100644 index 00000000..349456c4 --- /dev/null +++ b/docs/src/reference/asciidoc/Guardfile @@ -0,0 +1,13 @@ +require 'asciidoctor' +require 'erb' + +guard 'shell' do + watch(/^.*\.adoc$/) {|m| + Asciidoctor.render_file(m[0], :to_dir => "build/", :safe => Asciidoctor::SafeMode::UNSAFE, :attributes=> {'idprefix' => '', 'idseparator' => '-', 'copycss' => '', 'icons' => 'font', 'source-highlighter' => 'prettify', 'sectanchors' => '', 'doctype' => 'book','toc2' => '', 'spring-hadoop-version' => '2.1.0.BUILD-SNAPSHOT','spring-version' => '4.1.3.RELEASE', 'revnumber' => '2.1.0.BUILD-SNAPSHOT', 'numbered'=>'' }) + } +end + +guard 'livereload' do + watch(%r{build/.+\.(css|js|html)$}) +end + diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc new file mode 100644 index 00000000..aefe0aeb --- /dev/null +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -0,0 +1,16 @@ +[[appendices]] += Appendices + +:numbered!: + +[appendix] +== State Machine Concepts +This appendix provides generic information about state machines. + +=== Glossary + +state machine:: +machine of sort. + +state:: +a stage in a state machine. diff --git a/docs/src/reference/asciidoc/index-docinfo.xml b/docs/src/reference/asciidoc/index-docinfo.xml new file mode 100644 index 00000000..3bfb5a01 --- /dev/null +++ b/docs/src/reference/asciidoc/index-docinfo.xml @@ -0,0 +1,22 @@ +{revnumber} +Spring Statemachine + + + Janne + Valkealahti + Pivotal + + + + + 2015 + Pivotal Software, Inc. + + + + Copies of this document may be made for your own use and for + distribution to others, provided that you do not charge any fee for such + copies and further provided that each copy contains this Copyright + Notice, whether distributed in print or electronically. + + diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc new file mode 100644 index 00000000..3dc1ac1a --- /dev/null +++ b/docs/src/reference/asciidoc/index.adoc @@ -0,0 +1,37 @@ +:hadoop-Configuration: http://hadoop.apache.org/docs/r{hadoop-version}/api/org/apache/hadoop/conf/Configuration.html +:hadoop-FileSystem: http://hadoop.apache.org/docs/r{hadoop-version}/api/org/apache/hadoop/fs/FileSystem.html +:hadoop-Security: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-common/SecureMode.html +:hadoop-Streaming: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-mapreduce-client/hadoop-mapreduce-client-core/HadoopStreaming.html +:hadoop-DistributedCache: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-mapreduce-client/hadoop-mapreduce-client-core/DistributedCacheDeploy.html +:hadoop-WordCount: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html#Example:_WordCount_v1.0 +:hadoop-FileSystemShell: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-common/FileSystemShell.html +:hadoop-WebHdfs: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-hdfs/WebHDFS.html +:hadoop-Hftp: http://hadoop.apache.org/docs/r{hadoop-version}/hadoop-project-dist/hadoop-hdfs/Hftp.html +:java-ClassLoader: http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html +:core-ApplicationContext: http://docs.spring.io/spring/docs/{spring-version}/javadoc-api/org/springframework/context/ApplicationContext.html +:core-beans-factory-placeholderconfigurer: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/beans.html#beans-factory-placeholderconfigurer +:core-beans-environment: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/beans.html#beans-environment +:core-ResourcePatternResolver: http://docs.spring.io/spring/docs/{spring-version}/javadoc-api/org/springframework/core/io/support/ResourcePatternResolver.html +:core-ref-util: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/xsd-config.html#xsd-config-body-schemas-util-properties +:core-aop-schema-advisors: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/aop.html#aop-schema-advisors +:core-dao: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/dao.html +:core-dao-exceptions: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/dao.html#dao-exceptions +:core-jdbc: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html +:core-jdbc-JdbcTemplate: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate +:shdp-FsShell: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/fs/FsShell.html +:shdp-DistCp: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/fs/DistCp.html +:shdp-HdfsResourceLoader: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/fs/HdfsResourceLoader.html +:shdp-SpringDataStoreTextWriterConfigurerAdapter: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/config/annotation/SpringDataStoreTextWriterConfigurerAdapter.html +:shdp-EnableDataStoreTextWriter: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/config/annotation/EnableDataStoreTextWriter.html +:shdp-DataStoreTextWriterConfigurer: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/config/annotation/builders/DataStoreTextWriterConfigurer.html +:shdp-DataStoreWriter: http://docs.spring.io/spring-hadoop/docs/{spring-hadoop-version}/api/org/springframework/data/hadoop/store/DataStoreWriter.html +:spring-data-hadoop-boot-jar: spring-data-hadoop-boot-{spring-hadoop-version}.jar + += Spring Statemachine - Reference Documentation + +include::preface.adoc[] +include::introduction.adoc[] + +[[springandhadoop]] +include::sm.adoc[] +include::appendix.adoc[] diff --git a/docs/src/reference/asciidoc/introduction.adoc b/docs/src/reference/asciidoc/introduction.adoc new file mode 100644 index 00000000..c6129e8e --- /dev/null +++ b/docs/src/reference/asciidoc/introduction.adoc @@ -0,0 +1,5 @@ +[[introduction]] += Introduction + +== Requirements +TBD diff --git a/docs/src/reference/asciidoc/preface.adoc b/docs/src/reference/asciidoc/preface.adoc new file mode 100644 index 00000000..c2e716ca --- /dev/null +++ b/docs/src/reference/asciidoc/preface.adoc @@ -0,0 +1,4 @@ +[preface] +== Preface +Spring Statemachine is a framework for application developers to use +state machine concepts with Spring. diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc new file mode 100644 index 00000000..8c282753 --- /dev/null +++ b/docs/src/reference/asciidoc/sm.adoc @@ -0,0 +1,5 @@ +[[statemachine]] += State Machine +TBD + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..a7b02616 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +version=1.0.0.BUILD-SNAPSHOT +springVersion = 4.1.4.RELEASE +hamcrestVersion = 1.3 +junitVersion = 4.11 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..c97a8bdb Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..413b12f6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 31 16:49:22 GMT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/publish-maven.gradle b/publish-maven.gradle new file mode 100644 index 00000000..a598ddbf --- /dev/null +++ b/publish-maven.gradle @@ -0,0 +1,60 @@ +apply plugin: 'maven' + +ext.optionalDeps = [] +ext.providedDeps = [] + +ext.optional = { optionalDeps << it } +ext.provided = { providedDeps << it } + +install { + repositories.mavenInstaller { + customizePom(pom, project) + } +} + +def customizePom(pom, gradleProject) { + pom.whenConfigured { generatedPom -> + // respect 'optional' and 'provided' dependencies + gradleProject.optionalDeps.each { dep -> + generatedPom.dependencies.findAll { it.artifactId == dep.name }*.optional = true + } + gradleProject.providedDeps.each { dep -> + generatedPom.dependencies.findAll { it.artifactId == dep.name }*.scope = 'provided' + } + + // eliminate test-scoped dependencies (no need in maven central poms) + generatedPom.dependencies.removeAll { dep -> + dep.scope == 'test' + } + + // add all items necessary for maven central publication + generatedPom.project { + name = gradleProject.description + description = gradleProject.description + url = 'https://github.com/spring-projects/spring-statemachine' + organization { + name = 'SpringSource' + url = 'http://spring.io/spring-statemachine' + } + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + url = 'https://github.com/spring-projects/spring-statemachine' + connection = 'scm:git:git://github.com/spring-projects/spring-statemachine' + developerConnection = 'scm:git:git://github.com/spring-projects/spring-statemachine' + } + developers { + developer { + id = 'jvalkeal' + name = 'Janne Valkealahti' + email = 'janne.valkealahti@gmail.com' + } + } + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..0786e4e6 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'spring-statemachine' + +include 'spring-statemachine-core' diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java new file mode 100644 index 00000000..df1b1aa6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import java.util.Collection; + +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.support.AbstractStateMachine; +import org.springframework.statemachine.transition.Transition; + +/** + * Specialisation of a {@link StateMachine} using enums + * as its {@link State} and event types. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class EnumStateMachine, E extends Enum> extends AbstractStateMachine { + + /** + * Instantiates a new enum state machine. + * + * @param states the states + * @param transitions the transitions + * @param initialState the initial state + */ + public EnumStateMachine(Collection> states, Collection> transitions, + State initialState) { + super(states, transitions, initialState); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java new file mode 100644 index 00000000..8746c1a7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/ExtendedState.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import java.util.Map; + +/** + * Extended states are used to supplement state machine with a variables. If + * extended state is used a complete condition of a state machine is a + * combination of its state an extended state variables. + * + * @author Janne Valkealahti + * + */ +public interface ExtendedState { + + /** + * Gets the extended state variables. + * + * @return the extended state variables + */ + Map getVariables(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java new file mode 100644 index 00000000..a07c77c9 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.Transition; + +/** + * {@code StateContext} is representing a current context used in + * {@link Transition}s, {@link Action}s and {@link Guard}s order to get access + * to event headers and {@link ExtendedState}. + * + * @author Janne Valkealahti + * + */ +public interface StateContext { + + /** + * Gets the event message headers. + * + * @return the event message headers + */ + MessageHeaders getMessageHeaders(); + + /** + * Gets the state machine extended state. + * + * @return the state machine extended state + */ + ExtendedState getExtendedState(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java new file mode 100644 index 00000000..154f427b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import org.springframework.messaging.Message; +import org.springframework.statemachine.listener.StateMachineListener; + +/** + * {@code StateMachine} provides an APIs for generic finite state machine needed + * for basic operations like working with states, events and a lifecycle. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachine { + + /** + * Gets the initial state {@code S}. + * + * @return initial state + */ + S getInitialState(); + + /** + * Gets the current state {@code S}. + * + * @return current state + */ + S getState(); + + /** + * Start the state machine. + */ + void start(); + + /** + * Send an event {@code E} wrapped with a {@link Message} to the state + * machine. + * + * @param event the wrapped event to send + */ + void sendEvent(Message event); + + /** + * Send an event {@code E} to the state machine. + * + * @param event the event to send + */ + void sendEvent(E event); + + /** + * Adds the state listener. + * + * @param listener the listener + */ + void addStateListener(StateMachineListener listener); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java new file mode 100644 index 00000000..38c91c5b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachineSystemConstants.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 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 + * + * http://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; + +/** + * Various constants used in state machine lib. + * + * @author Janne Valkealahti + * + */ +public abstract class StateMachineSystemConstants { + + /** Default bean id for state machine. */ + public static final String DEFAULT_ID_STATEMACHINE = "stateMachine"; + + /** Default bean id for state machine factory. */ + public static final String DEFAULT_ID_STATEMACHINEFACTORY = "stateMachineFactory"; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java new file mode 100644 index 00000000..41a10ee3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.action; + +import org.springframework.statemachine.StateContext; + +/** + * Generic strategy interface used by a state machine to respond + * events by executing an {@code Action} with a {@link StateContext}. + * + * @author Janne Valkealahti + * + */ +public interface Action { + + /** + * Execute action with a {@link StateContext}. + * + * @param context the state context + */ + void execute(StateContext context); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java new file mode 100644 index 00000000..52c2facf --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/OnTransition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 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 + * + * http://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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface OnTransition { + + String source() default ""; + + String target() default ""; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java new file mode 100644 index 00000000..4d2df8a9 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/annotation/WithStateMachine.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Component +public @interface WithStateMachine { + + String name() default ""; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java new file mode 100644 index 00000000..1f9da39f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachine.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; +import org.springframework.statemachine.config.configuration.StateMachineConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({StateMachineConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableStateMachine { + + /** + * The name of bean, or if plural, aliases for bean created based on this + * annotation. If left unspecified bean name will be autogenerated. + * + * @see Bean#name() + * @return the array if names or empty as default + */ + String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE}; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java new file mode 100644 index 00000000..6ebe5b94 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnableStateMachineFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; +import org.springframework.statemachine.config.configuration.StateMachineFactoryConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({StateMachineFactoryConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableStateMachineFactory { + + /** + * The name of bean, or if plural, aliases for bean created based on this + * annotation. If left unspecified bean name will be autogenerated. + * + * @see Bean#name() + * @return the array if names or empty as default + */ + String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY}; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java new file mode 100644 index 00000000..75c03de1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineConfigurerAdapter.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +public class EnumStateMachineConfigurerAdapter, E extends Enum> extends StateMachineConfigurerAdapter { + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java new file mode 100644 index 00000000..2dc61697 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.builders.StateMachineStates.StateData; +import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData; +import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.support.LifecycleObjectSupport; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.DefaultInternalTransition; +import org.springframework.statemachine.transition.Transition; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class EnumStateMachineFactory, E extends Enum> extends LifecycleObjectSupport implements + StateMachineFactory, E> { + + private final StateMachineTransitions stateMachineTransitions; + + private final StateMachineStates stateMachineStates; + + /** + * Instantiates a new enum state machine factory. + * + * @param stateMachineTransitions the state machine transitions + * @param stateMachineStates the state machine states + */ + public EnumStateMachineFactory(StateMachineTransitions stateMachineTransitions, + StateMachineStates stateMachineStates) { + this.stateMachineTransitions = stateMachineTransitions; + this.stateMachineStates = stateMachineStates; + } + + @Override + public StateMachine, E> getStateMachine() { + return stateMachine(); + } + + public StateMachine, E> stateMachine() { + Map> stateMap = new HashMap>(); + for (StateData stateData : stateMachineStates.getStates()) { + stateMap.put(stateData.getState(), new EnumState(stateData.getState(), stateData.getDeferred(), + stateData.getEntryActions(), stateData.getExitActions())); + } + + Collection> transitions = new ArrayList>(); + for (TransitionData transitionData : stateMachineTransitions.getTransitions()) { + S source = transitionData.getSource(); + S target = transitionData.getTarget(); + E event = transitionData.getEvent(); + if (transitionData.getKind() == TransitionKind.EXTERNAL) { + DefaultExternalTransition transition = new DefaultExternalTransition(stateMap.get(source), + stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard()); + transitions.add(transition); + + } else if (transitionData.getKind() == TransitionKind.INTERNAL) { + DefaultInternalTransition transition = new DefaultInternalTransition(stateMap.get(source), + transitionData.getActions(), event, transitionData.getGuard()); + transitions.add(transition); + } + } + + EnumStateMachine machine = new EnumStateMachine(stateMap.values(), transitions, + stateMap.get(stateMachineStates.getInitialState())); + machine.afterPropertiesSet(); + if (getBeanFactory() != null) { + machine.setBeanFactory(getBeanFactory()); + } + machine.setAutoStartup(isAutoStartup()); + machine.start(); + return machine; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java new file mode 100644 index 00000000..38ef84c8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; + +public class StateMachineConfig { + + public final StateMachineTransitions transitions; + + public final StateMachineStates states; + + public StateMachineConfig(StateMachineTransitions transitions, StateMachineStates states) { + this.transitions = transitions; + this.states = states; + } + + public StateMachineTransitions getTransitions() { + return transitions; + } + + public StateMachineStates getStates() { + return states; + } +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java new file mode 100644 index 00000000..79798acd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineConfigurerAdapter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateBuilder; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; + +public class StateMachineConfigurerAdapter implements StateMachineConfigurer { + + private StateMachineTransitionBuilder transitionBuilder; + private StateMachineStateBuilder stateBuilder; + + @Override + public final void init(StateMachineConfigBuilder config) throws Exception { + config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder()); + config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder()); + } + + @Override + public void configure(StateMachineConfigBuilder config) throws Exception { + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + } + + @Override + public boolean isAssignable(AnnotationBuilder> builder) { + return builder instanceof StateMachineConfigBuilder; + } + + protected final StateMachineTransitionBuilder getStateMachineTransitionBuilder() throws Exception { + if (transitionBuilder != null) { + return transitionBuilder; + } + transitionBuilder = new StateMachineTransitionBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(transitionBuilder); + return transitionBuilder; + } + + protected final StateMachineStateBuilder getStateMachineStateBuilder() throws Exception { + if (stateBuilder != null) { + return stateBuilder; + } + stateBuilder = new StateMachineStateBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true); + configure(stateBuilder); + return stateBuilder; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java new file mode 100644 index 00000000..6a3f2a97 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateMachineFactory.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import org.springframework.statemachine.StateMachine; + +public interface StateMachineFactory { + + StateMachine getStateMachine(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java new file mode 100644 index 00000000..df70a85b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; + +public class StateMachineConfigBuilder + extends + AbstractConfiguredAnnotationBuilder, StateMachineConfigBuilder, StateMachineConfigBuilder> { + + @SuppressWarnings("unchecked") + @Override + protected StateMachineConfig performBuild() throws Exception { + StateMachineTransitionBuilder sharedObject = getSharedObject(StateMachineTransitionBuilder.class); + StateMachineStateBuilder sharedObject2 = getSharedObject(StateMachineStateBuilder.class); + StateMachineStates states = (StateMachineStates) sharedObject2.build(); + StateMachineTransitions transitions = (StateMachineTransitions) sharedObject.build(); + StateMachineConfig bean = new StateMachineConfig(transitions, states); + return bean; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java new file mode 100644 index 00000000..9658e47e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineConfigurer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +public interface StateMachineConfigurer extends + AnnotationConfigurer, StateMachineConfigBuilder> { + + void configure(StateMachineStateConfigurer transitions) throws Exception; + + void configure(StateMachineTransitionConfigurer transitions) throws Exception; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java new file mode 100644 index 00000000..fcb7c48b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.statemachine.config.builders.StateMachineStates.StateData; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.statemachine.config.configurers.DefaultStateConfigurer; +import org.springframework.statemachine.config.configurers.StateConfigurer; + +public class StateMachineStateBuilder + extends AbstractConfiguredAnnotationBuilder, StateMachineStateConfigurer, StateMachineStateBuilder> + implements StateMachineStateConfigurer { + + private Collection> states = new ArrayList>(); + private S initialState; + + public StateMachineStateBuilder() { + super(); + } + + public StateMachineStateBuilder(ObjectPostProcessor objectPostProcessor, + boolean allowConfigurersOfSameType) { + super(objectPostProcessor, allowConfigurersOfSameType); + } + + public StateMachineStateBuilder(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + protected StateMachineStates performBuild() throws Exception { + StateMachineStates bean = new StateMachineStates(initialState, states); + return bean; + } + + @Override + public StateConfigurer withStates() throws Exception { + return apply(new DefaultStateConfigurer()); + } + + public void add(Collection> states) { + this.states.addAll(states); + } + + public void setInitialState(S state) { + this.initialState = state; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java new file mode 100644 index 00000000..c2b053d1 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateConfigurer.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import org.springframework.statemachine.config.configurers.StateConfigurer; + +public interface StateMachineStateConfigurer { + + StateConfigurer withStates() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java new file mode 100644 index 00000000..aff6a033 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +public class StateMachineStates { + + private Collection> states; + + private final S initialState; + + public StateMachineStates(S initialState, Collection> states) { + this.states = states; + this.initialState = initialState; + } + + public Collection> getStates() { + return states; + } + + public S getInitialState() { + return initialState; + } + + public static class StateData { + private S state; + private Collection deferred; + private Collection entryActions; + private Collection exitActions; + public StateData(S state, Collection deferred) { + this(state, deferred, null, null); + } + public StateData(S state, Collection deferred, Collection entryActions, Collection exitActions) { + this.state = state; + this.deferred = deferred; + this.entryActions = entryActions; + this.exitActions = exitActions; + } + public StateData(S state, E[] deferred) { + this(state, deferred != null ? Arrays.asList(deferred) : new ArrayList()); + } + public S getState() { + return state; + } + public Collection getDeferred() { + return deferred; + } + public Collection getEntryActions() { + return entryActions; + } + public Collection getExitActions() { + return exitActions; + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java new file mode 100644 index 00000000..ec59f9fb --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.TransitionKind; + +public class StateMachineTransitionBuilder + extends + AbstractConfiguredAnnotationBuilder, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements StateMachineTransitionConfigurer { + + private Collection> transitionData = new ArrayList>(); + + public StateMachineTransitionBuilder() { + super(); + } + + public StateMachineTransitionBuilder(ObjectPostProcessor objectPostProcessor, + boolean allowConfigurersOfSameType) { + super(objectPostProcessor, allowConfigurersOfSameType); + } + + public StateMachineTransitionBuilder(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + protected StateMachineTransitions performBuild() throws Exception { + StateMachineTransitions bean = new StateMachineTransitions(transitionData); + return bean; + } + + @Override + public ExternalTransitionConfigurer withExternal() throws Exception { + return apply(new DefaultExternalTransitionConfigurer()); + } + + @Override + public InternalTransitionConfigurer withInternal() throws Exception { + return apply(new DefaultInternalTransitionConfigurer()); + } + + public void add(S source, S target, E event, Collection actions, Guard guard, TransitionKind kind) { + transitionData.add(new TransitionData(source, target, event, actions, guard, kind)); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java new file mode 100644 index 00000000..a1cc39a4 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; + +public interface StateMachineTransitionConfigurer { + + ExternalTransitionConfigurer withExternal() throws Exception; + + InternalTransitionConfigurer withInternal() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java new file mode 100644 index 00000000..f71a8a15 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 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 + * + * http://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.config.builders; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.TransitionKind; + +public class StateMachineTransitions { + + private Collection> transitions; + + public StateMachineTransitions(Collection> transitions) { + this.transitions = transitions; + } + + public Collection> getTransitions() { + return transitions; + } + + public static class TransitionData { + S source; + S target; + E event; + Collection actions; + Guard guard; + TransitionKind kind; + public TransitionData(S source, S target, E event, Collection actions, Guard guard, TransitionKind kind) { + this.source = source; + this.target = target; + this.event = event; + this.actions = actions; + this.guard = guard; + this.kind = kind; + } + public S getSource() { + return source; + } + public S getTarget() { + return target; + } + public E getEvent() { + return event; + } + public Collection getActions() { + return actions; + } + public Guard getGuard() { + return guard; + } + public TransitionKind getKind() { + return kind; + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java new file mode 100644 index 00000000..039e9241 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationBuilder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A base {@link AnnotationBuilder} that ensures the object being built is only + * built one time. + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param the type of Object that is being built + */ +public abstract class AbstractAnnotationBuilder implements AnnotationBuilder { + + /** Flag tracking build */ + private AtomicBoolean building = new AtomicBoolean(); + + /** Built object is stored here */ + private O object; + + @Override + public final O build() throws Exception { + if(building.compareAndSet(false, true)) { + object = doBuild(); + return object; + } + throw new IllegalStateException("This object has already been built"); + } + + /** + * Gets the object that was built. If it has not been built yet an Exception + * is thrown. + * + * @return the Object that was built + */ + public final O getObject() { + if(!building.get()) { + throw new IllegalStateException("This object has not been built"); + } + return object; + } + + /** + * Subclasses should implement this to perform the build. + * + * @return the object that should be returned by {@link #build()}. + * @throws Exception if an error occurs + */ + protected abstract O doBuild() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java new file mode 100644 index 00000000..546f2ea7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractAnnotationConfiguration.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.ClassUtils; + +/** + * Base implementation of @{@link Configuration} class. + * + * @author Janne Valkealahti + * + * @param The object that used builder returns + * @param The type of the builder + */ +public abstract class AbstractAnnotationConfiguration, O> + implements ImportAware, BeanClassLoaderAware { + + private List> configurers; + + private ClassLoader beanClassLoader; + + private AnnotationAttributes annotationAttributes; + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + beanClassLoader = classLoader; + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + Map enableConfigurationAttrMap = + importMetadata.getAnnotationAttributes(EnableAnnotationConfiguration.class.getName()); + AnnotationAttributes enableConfigurationAttrs = AnnotationAttributes.fromMap(enableConfigurationAttrMap); + if(enableConfigurationAttrs == null) { + // search parent classes + Class currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader); + for(Class classToInspect = currentClass ;classToInspect != null; classToInspect = classToInspect.getSuperclass()) { + EnableAnnotationConfiguration enableConfigurationAnnotation = + AnnotationUtils.findAnnotation(classToInspect, EnableAnnotationConfiguration.class); + if(enableConfigurationAnnotation == null) { + continue; + } + enableConfigurationAttrMap = AnnotationUtils + .getAnnotationAttributes(enableConfigurationAnnotation); + enableConfigurationAttrs = AnnotationAttributes.fromMap(enableConfigurationAttrMap); + } + } + annotationAttributes = enableConfigurationAttrs; + } + + /** + * Sets the configurers. + * + * @param configurers the configurers + * @throws Exception the exception + */ + @Autowired(required=false) + public void setConfigurers(List> configurers) throws Exception { + this.configurers = configurers; + onConfigurers(configurers); + } + + public AnnotationAttributes getAnnotationAttributes() { + return annotationAttributes; + } + + public List> getConfigurers() { + return configurers; + } + + protected abstract void onConfigurers(List> configurers) throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java new file mode 100644 index 00000000..7e273359 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractConfiguredAnnotationBuilder.java @@ -0,0 +1,531 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; + +/** + * A base {@link AnnotationBuilder} that allows {@link AnnotationConfigurer}s to be + * applied to it. This makes modifying the {@link AnnotationBuilder} a strategy + * that can be customised and broken up into a number of + * {@link AnnotationConfigurer} objects that have more specific goals than that + * of the {@link AnnotationBuilder}. + *

+ * + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The object that this builder returns + * @param The interface of type B + * @param The type of this builder (that is returned by the base class) + */ +public abstract class AbstractConfiguredAnnotationBuilder> + extends AbstractAnnotationBuilder { + + private final static Log log = LogFactory.getLog(AbstractConfiguredAnnotationBuilder.class); + + /** Configurers which are added to this builder before the configure step */ + private final LinkedHashMap>, List>> mainConfigurers = + new LinkedHashMap>, List>>(); + + /** Configurers which are added to this builder during the configuration phase */ + private final LinkedHashMap>, List>> postConfigurers = + new LinkedHashMap>, List>>(); + + private final Map, Object> sharedObjects = new HashMap, Object>(); + + private final boolean allowConfigurersOfSameType; + + /** Current state of this builder */ + private BuildState buildState = BuildState.UNBUILT; + + private ObjectPostProcessor objectPostProcessor; + + /** + * Instantiates a new annotation builder. + */ + protected AbstractConfiguredAnnotationBuilder() { + this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR); + } + + /** + * Instantiates a new annotation builder. + * + * @param objectPostProcessor the object post processor + */ + protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor objectPostProcessor) { + this(objectPostProcessor,false); + } + + /** + * Instantiates a new annotation builder. + * + * @param objectPostProcessor the object post processor + * @param allowConfigurersOfSameType the allow configurers of same type + */ + protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor objectPostProcessor, boolean allowConfigurersOfSameType) { + Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null"); + this.objectPostProcessor = objectPostProcessor; + this.allowConfigurersOfSameType = allowConfigurersOfSameType; + } + + @Override + protected final O doBuild() throws Exception { + synchronized (mainConfigurers) { + buildState = BuildState.INITIALIZING_MAINS; + beforeInit(); + initMainConfigurers(); + + buildState = BuildState.CONFIGURING_MAINS; + beforeConfigureMains(); + configureMainConfigurers(); + + buildState = BuildState.CONFIGURING_POSTS; + beforeConfigurePosts(); + configurePostConfigurers(); + + buildState = BuildState.BUILDING; + O result = performBuild(); + + buildState = BuildState.BUILT; + return result; + } + } + + /** + * Similar to {@link #build()} and {@link #getObject()} but checks the state + * to determine if {@link #build()} needs to be called first. + * + * @return the result of {@link #build()} or {@link #getObject()}. If an + * error occurs while building, returns null. + */ + public O getOrBuild() { + if (isUnbuilt()) { + try { + return build(); + } catch (Exception e) { + log.error("Failed to perform build. Returning null", e); + return null; + } + } else { + return getObject(); + } + } + + /** + * Applies a {@link AnnotationConfigurerAdapter} to this + * {@link AnnotationBuilder} and invokes + * {@link AnnotationConfigurerAdapter#setBuilder(AnnotationBuilder)}. + * + * @param configurer the configurer + * @param type of AnnotationConfigurer + * @return Configurer passed as parameter + * @throws Exception if error occurred + */ + @SuppressWarnings("unchecked") + public > C apply(C configurer) throws Exception { + add(configurer); + configurer.addObjectPostProcessor(objectPostProcessor); + configurer.setBuilder((B) this); + return configurer; + } + + /** + * Similar to {@link #apply(AnnotationConfigurer)} but checks the state + * to determine if {@link #apply(AnnotationConfigurer)} needs to be called first. + * + * @param configurer the configurer + * @param type of AnnotationConfigurer + * @return Configurer passed as parameter + * @throws Exception if error occurred + */ + @SuppressWarnings("unchecked") + public > C getOrApply(C configurer) throws Exception { + C existing = (C) getConfigurer(configurer.getClass()); + if (existing != null) { + return existing; + } + return apply(configurer); + } + + /** + * Applies a {@link AnnotationConfigurer} to this {@link AnnotationBuilder} + * overriding any {@link AnnotationConfigurer} of the exact same class. Note + * that object hierarchies are not considered. + * + * @param configurer the configurer + * @param type of AnnotationConfigurer + * @return Configurer passed as parameter + * @throws Exception if error occurred + */ + public > C apply(C configurer) throws Exception { + add(configurer); + return configurer; + } + + /** + * Sets an object that is shared by multiple {@link AnnotationConfigurer}. + * + * @param sharedType the Class to key the shared object by. + * @param object the Object to store + * @param type of share object + */ + @SuppressWarnings("unchecked") + public void setSharedObject(Class sharedType, C object) { + this.sharedObjects.put((Class) sharedType, object); + } + + /** + * Gets a shared Object. Note that object hierarchies are not considered. + * + * @param sharedType the type of the shared Object + * @param type of share object + * @return the shared Object or null if it is not found + */ + @SuppressWarnings("unchecked") + public C getSharedObject(Class sharedType) { + return (C) this.sharedObjects.get(sharedType); + } + + /** + * Gets the shared objects. + * + * @return Shared objects + */ + public Map, Object> getSharedObjects() { + return Collections.unmodifiableMap(this.sharedObjects); + } + + /** + * Adds {@link AnnotationConfigurer} ensuring that it is allowed and + * invoking {@link AnnotationConfigurer#init(AnnotationBuilder)} immediately + * if necessary. + * + * @param configurer the {@link AnnotationConfigurer} to add + * @param type of AnnotationConfigurer + * @throws Exception if an error occurs + */ + @SuppressWarnings("unchecked") + private > void add(C configurer) throws Exception { + Assert.notNull(configurer, "configurer cannot be null"); + + Class> clazz = + (Class>) configurer.getClass(); + + if (!buildState.isConfigured()) { + synchronized (mainConfigurers) { + List> configs = allowConfigurersOfSameType ? this.mainConfigurers.get(clazz) : null; + if (configs == null) { + configs = new ArrayList>(1); + } + configs.add(configurer); + this.mainConfigurers.put(clazz, configs); + if (buildState.isInitializing()) { + configurer.init((B) this); + } + } + } else { + synchronized (postConfigurers) { + List> configs = allowConfigurersOfSameType ? this.postConfigurers.get(clazz) : null; + if (configs == null) { + configs = new ArrayList>(1); + } + configs.add(configurer); + this.postConfigurers.put(clazz, configs); + configurer.init((B) this); + } + } + } + + /** + * Gets all the {@link AnnotationConfigurer} instances by its class name or an + * empty List if not found. Note that object hierarchies are not considered. + * + * @param clazz the {@link AnnotationConfigurer} class to look for + * @param type of AnnotationConfigurer + * @return All configurers + */ + @SuppressWarnings("unchecked") + public > List getConfigurers(Class clazz) { + List configs = (List) this.mainConfigurers.get(clazz); + if (configs == null) { + return new ArrayList(); + } + return new ArrayList(configs); + } + + /** + * Removes all the {@link AnnotationConfigurer} instances by its class name or an + * empty List if not found. Note that object hierarchies are not considered. + * + * @param clazz the {@link AnnotationConfigurer} class to look for + * @param type of AnnotationConfigurer + * @return Empty list of configurers + */ + @SuppressWarnings("unchecked") + public > List removeConfigurers(Class clazz) { + List configs = (List) this.mainConfigurers.remove(clazz); + if (configs == null) { + return new ArrayList(); + } + return new ArrayList(configs); + } + + /** + * Gets the {@link AnnotationConfigurer} by its class name or + * null if not found. Note that object hierarchies are not + * considered. + * + * @param clazz the configurer class type + * @param type of AnnotationConfigurer + * @return Matched configurers + */ + @SuppressWarnings("unchecked") + public > C getConfigurer(Class clazz) { + List> configs = this.mainConfigurers.get(clazz); + if (configs == null) { + return null; + } + if (configs.size() != 1) { + throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs); + } + return (C) configs.get(0); + } + + /** + * Removes and returns the {@link AnnotationConfigurer} by its class name or + * null if not found. Note that object hierarchies are not + * considered. + * + * @param clazz the configurer class type + * @param type of AnnotationConfigurer + * @return Matched configurers + */ + @SuppressWarnings("unchecked") + public > C removeConfigurer(Class clazz) { + List> configs = this.mainConfigurers.remove(clazz); + if (configs == null) { + return null; + } + if (configs.size() != 1) { + throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs); + } + return (C) configs.get(0); + } + + /** + * Specifies the {@link ObjectPostProcessor} to use. + * @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null + * @return the {@link AnnotationBuilder} for further customizations + */ + @SuppressWarnings("unchecked") + public O objectPostProcessor(ObjectPostProcessor objectPostProcessor) { + Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null"); + this.objectPostProcessor = objectPostProcessor; + return (O) this; + } + + /** + * Performs post processing of an object. The default is to delegate to the + * {@link ObjectPostProcessor}. + * + * @param object the Object to post process + * @param

type of processed object + * @return the possibly modified Object to use + */ + protected

P postProcess(P object) { + return (P) this.objectPostProcessor.postProcess(object); + } + + /** + * Invoked prior to invoking each + * {@link AnnotationConfigurer#init(AnnotationBuilder)} method. Subclasses may + * override this method to hook into the lifecycle without using a + * {@link AnnotationConfigurer}. + * + * @throws Exception if error occurred + */ + protected void beforeInit() throws Exception { + } + + /** + * Invoked prior to invoking each main + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} method. + * Subclasses may override this method to hook into the lifecycle without + * using a {@link AnnotationConfigurer}. + * + * @throws Exception if error occurred + */ + protected void beforeConfigureMains() throws Exception { + } + + /** + * Invoked prior to invoking each post + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} method. + * Subclasses may override this method to hook into the lifecycle without + * using a {@link AnnotationConfigurer}. + * + * @throws Exception if error occurred + */ + protected void beforeConfigurePosts() throws Exception { + } + + /** + * Subclasses must implement this method to build the object that is being returned. + * + * @return Object build by this builder + * @throws Exception if error occurred + */ + protected abstract O performBuild() throws Exception; + + @SuppressWarnings("unchecked") + private void initMainConfigurers() throws Exception { + for (AnnotationConfigurer configurer : getMainConfigurers()) { + configurer.init((B) this); + } + } + + @SuppressWarnings("unchecked") + private void configureMainConfigurers() throws Exception { + for (AnnotationConfigurer configurer : getMainConfigurers()) { + configurer.configure((B) this); + } + } + + @SuppressWarnings("unchecked") + private void configurePostConfigurers() throws Exception { + for (AnnotationConfigurer configurer : getPostConfigurers()) { + configurer.configure((B) this); + } + } + + /** + * Gets all configurers. + * + * @return the configurers + */ + private Collection> getMainConfigurers() { + List> result = new ArrayList>(); + for (List> configs : this.mainConfigurers.values()) { + result.addAll(configs); + } + return result; + } + + private Collection> getPostConfigurers() { + List> result = new ArrayList>(); + for (List> configs : this.postConfigurers.values()) { + result.addAll(configs); + } + return result; + } + + /** + * Determines if the object is unbuilt. + * + * @return true, if unbuilt else false + */ + private boolean isUnbuilt() { + synchronized (mainConfigurers) { + return buildState == BuildState.UNBUILT; + } + } + + /** + * The build state for the application + */ + private static enum BuildState { + + /** + * This is the state before the {@link AnnotationBuilder#build()} is invoked + */ + UNBUILT(0), + + /** + * The state from when {@link AnnotationBuilder#build()} is first invoked until + * all the {@link AnnotationConfigurer#init(AnnotationBuilder)} methods have + * been invoked. + */ + INITIALIZING_MAINS(1), + + /** + * The state from after all main + * {@link AnnotationConfigurer#init(AnnotationBuilder)} + * have been invoked until after all the + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} + * methods have been invoked. + */ + CONFIGURING_MAINS(2), + + /** + * The state from after all post + * {@link AnnotationConfigurer#init(AnnotationBuilder)} + * have been invoked until after all the + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} + * methods have been invoked. + */ + CONFIGURING_POSTS(3), + + /** + * From the point after all the + * {@link AnnotationConfigurer#configure(AnnotationBuilder)} + * have completed to just after + * {@link AbstractConfiguredAnnotationBuilder#performBuild()}. + */ + BUILDING(4), + + /** + * After the object has been completely built. + */ + BUILT(5); + + private final int order; + + BuildState(int order) { + this.order = order; + } + + /** + * Checks if is initializing. + * + * @return true, if is initializing + */ + public boolean isInitializing() { + return INITIALIZING_MAINS.order == order; + } + + /** + * Determines if the state is CONFIGURING or later + * + * @return true, if configured + */ + public boolean isConfigured() { + return order >= CONFIGURING_MAINS.order; + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java new file mode 100644 index 00000000..0474946c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AbstractImportingAnnotationConfiguration.java @@ -0,0 +1,188 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.DefaultBeanNameGenerator; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Base class for {@link Configuration} which works on a bean definition level + * relying on {@link ImportBeanDefinitionRegistrar} phase to register beans. + * + * @author Janne Valkealahti + * + * @param The object that used builder returns + * @param The type of the builder + */ +public abstract class AbstractImportingAnnotationConfiguration, O> implements + ImportBeanDefinitionRegistrar, BeanFactoryAware, EnvironmentAware { + + private BeanFactory beanFactory; + + private Environment environment; + + private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + Class annotationType = getAnnotation(); + AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes( + annotationType.getName(), false)); + String[] names = attributes.getStringArray("name"); + + BeanDefinition beanDefinition; + try { + beanDefinition = buildBeanDefinition(); + } catch (Exception e) { + throw new RuntimeException("Error with onConfigurers", e); + } + + if (ObjectUtils.isEmpty(names)) { + // ok, name(s) not given, generate one + names = new String[] { beanNameGenerator.generateBeanName(beanDefinition, registry) }; + } + + registry.registerBeanDefinition(names[0], beanDefinition); + if (names.length > 1) { + for (int i = 1; i < names.length; i++) { + registry.registerAlias(names[0], names[i]); + } + } + } + + protected abstract static class BeanDelegatingFactoryBean, O> + implements FactoryBean, InitializingBean { + + private final B builder; + + private O object; + + private List> configurers; + + public BeanDelegatingFactoryBean(B builder){ + this.builder = builder; + } + + @Override + public abstract Class getObjectType(); + + @Override + public O getObject() throws Exception { + return object; + } + + @Override + public boolean isSingleton() { + return true; + } + +// @Override +// public void afterPropertiesSet() throws Exception { +// for (AnnotationConfigurer configurer : configurers) { +// if (configurer.isAssignable(builder)) { +// // we need builder.apply(configurer); +//// builder. +// } +// } +// // should be getOrBuild??? +// object = builder.build(); +// } + + @Autowired(required = false) + public void setConfigurers(List> configurers) { + this.configurers = configurers; + } + + public B getBuilder() { + return builder; + } + + public List> getConfigurers() { + return configurers; + } + + protected void setObject(O object) { + this.object = object; + } + + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, + "beanFactory be of type ListableBeanFactory but was " + beanFactory); + this.beanFactory = beanFactory; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + /** + * Called to get a bean definition to register. + * + * @return the bean definition to register + * @throws Exception if error occurred + */ + protected abstract BeanDefinition buildBeanDefinition() throws Exception; + + /** + * Gets the annotation specific for this configurer. + * + * @return the annotation + */ + protected abstract Class getAnnotation(); + + /** + * Gets the bean factory. + * + * @return the bean factory + */ + protected BeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * Gets the environment. + * + * @return the environment + */ + protected Environment getEnvironment() { + return environment; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java new file mode 100644 index 00000000..b8ccceea --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +/** + * Interface for building an {@link Object}. + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The type of the Object being built + */ +public interface AnnotationBuilder { + + /** + * Builds the object and returns it or null. + * + * @return the Object to be built or null if the implementation allows it. + * @throws Exception if an error occurred when building the Object + */ + O build() throws Exception; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java new file mode 100644 index 00000000..befdafc3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +/** + * Allows for configuring an {@link AnnotationBuilder}. All + * {@link AnnotationConfigurer}s first have their {@link #init(AnnotationBuilder)} + * method invoked. After all {@link #init(AnnotationBuilder)} methods have been + * invoked, each {@link #configure(AnnotationBuilder)} method is invoked. + * + * @see AbstractConfiguredAnnotationBuilder + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The object being built by the {@link AnnotationBuilder} B + * @param The {@link AnnotationBuilder} that builds objects of type O. This is + * also the {@link AnnotationBuilder} that is being configured. + */ +public interface AnnotationConfigurer> { + + /** + * Initialise the {@link AnnotationBuilder}. Here only shared state should be + * created and modified, but not properties on the {@link AnnotationBuilder} + * used for building the object. This ensures that the + * {@link #configure(AnnotationBuilder)} method uses the correct shared + * objects when building. + * + * @param builder the builder + * @throws Exception if error occurred + */ + void init(B builder) throws Exception; + + /** + * Configure the {@link AnnotationBuilder} by setting the necessary properties + * on the {@link AnnotationBuilder}. + * + * @param builder the builder + * @throws Exception if error occurred + */ + void configure(B builder) throws Exception; + + boolean isAssignable(AnnotationBuilder builder); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java new file mode 100644 index 00000000..227933d3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerAdapter.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.GenericTypeResolver; + +/** + * A base class for {@link AnnotationConfigurer} that allows subclasses to only + * implement the methods they are interested in. It also provides a mechanism + * for using the {@link AnnotationConfigurer} and when done gaining access to the + * {@link AnnotationBuilder} that is being configured. + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @param The Object being built by B + * @param The interface of type B + * @param The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter} + */ +public abstract class AnnotationConfigurerAdapter> + implements AnnotationConfigurer { + + private B builder; + + private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor(); + + @Override + public void init(B builder) throws Exception {} + + @Override + public void configure(B builder) throws Exception {} + + /** + * Return the {@link AnnotationBuilder} when done using the + * {@link AnnotationConfigurer}. This is useful for method chaining. + * + * @return the {@link AnnotationBuilder} + */ + @SuppressWarnings("unchecked") + public I and() { + // we're either casting to itself or its interface + return (I) getBuilder(); + } + + /** + * Gets the {@link AnnotationBuilder}. Cannot be null. + * + * @return the {@link AnnotationBuilder} + * @throws IllegalStateException if AnnotationBuilder is null + */ + protected final B getBuilder() { + if(builder == null) { + throw new IllegalStateException("annotationBuilder cannot be null"); + } + return builder; + } + + /** + * Adds an {@link ObjectPostProcessor} to be used for this adapter. The + * default implementation does nothing to the object. + * + * @param objectPostProcessor the {@link ObjectPostProcessor} to use + */ + public void addObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor); + } + + /** + * Sets the {@link AnnotationBuilder} to be used. This is automatically set + * when using + * {@link AbstractConfiguredAnnotationBuilder#apply(AnnotationConfigurerAdapter)} + * + * @param builder the {@link AnnotationBuilder} to set + */ + public void setBuilder(B builder) { + this.builder = builder; + } + + /** + * An {@link ObjectPostProcessor} that delegates work to numerous + * {@link ObjectPostProcessor} implementations. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static final class CompositeObjectPostProcessor implements ObjectPostProcessor { + + private List> postProcessors = new ArrayList>(); + + @Override + public Object postProcess(Object object) { + for(ObjectPostProcessor opp : postProcessors) { + Class oppClass = opp.getClass(); + Class oppType = GenericTypeResolver.resolveTypeArgument(oppClass,ObjectPostProcessor.class); + if(oppType == null || oppType.isAssignableFrom(object.getClass())) { + object = opp.postProcess(object); + } + } + return object; + } + + /** + * Adds an {@link ObjectPostProcessor} to use + * + * @param objectPostProcessor the {@link ObjectPostProcessor} to add + * @return true if the {@link ObjectPostProcessor} was added, else false + */ + private boolean addObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + return this.postProcessors.add(objectPostProcessor); + } + + } + + @Override + public boolean isAssignable(AnnotationBuilder builder) { + return true; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java new file mode 100644 index 00000000..4c4c59c9 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/AnnotationConfigurerBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +/** + * Interface for wrapping a return type from {@link AnnotationConfigurer} + * into {@link AnnotationBuilder}. + * + * @author Janne Valkealahti + * + * @param The parent return type of the configurer. + */ +public interface AnnotationConfigurerBuilder { + + /** + * Get a parent {@link AnnotationBuilder} working + * with a {@link AnnotationConfigurer}. + * @return The parent {@link AnnotationBuilder} + */ + I and(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java new file mode 100644 index 00000000..e6e015fe --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/EnableAnnotationConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Base annotation used in JavaConfig order to enable + * some base functionality. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface EnableAnnotationConfiguration { + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java new file mode 100644 index 00000000..a09a1802 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/ObjectPostProcessor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import org.springframework.beans.factory.Aware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Allows initialization of Objects. Typically this is used to call the + * {@link Aware} methods, {@link InitializingBean#afterPropertiesSet()}, and + * ensure that {@link DisposableBean#destroy()} has been invoked. + * + * @param the bound of the types of Objects this {@link ObjectPostProcessor} supports. + * + * @author Rob Winch + */ +public interface ObjectPostProcessor { + + /** + * Initialize the object possibly returning a modified instance that should + * be used instead. + * + * @param object the object to initialize + * @param the type of a processed object + * @return the initialized version of the object + */ + O postProcess(O object); + + /** + * A do nothing implementation of the {@link ObjectPostProcessor} + */ + ObjectPostProcessor QUIESCENT_POSTPROCESSOR = new ObjectPostProcessor() { + @Override + public T postProcess(T object) { + return object; + } + }; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java new file mode 100644 index 00000000..e09a3955 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configuration; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; +import org.springframework.util.Assert; + +/** + * Post processor handling lifecycle methods of + * POJOs created from builder/configurer. + * + * @author Janne Valkealahti + * + */ +final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor, DisposableBean, SmartLifecycle { + + private final static Log log = LogFactory.getLog(AutowireBeanFactoryObjectPostProcessor.class); + + private final AutowireCapableBeanFactory autowireBeanFactory; + private final List disposableBeans = new ArrayList(); + private final List lifecycleBeans = new ArrayList(); + + private boolean running; + + /** + * Instantiates a new autowire bean factory object post processor. + * + * @param autowireBeanFactory the autowire bean factory + */ + public AutowireBeanFactoryObjectPostProcessor(AutowireCapableBeanFactory autowireBeanFactory) { + Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null"); + this.autowireBeanFactory = autowireBeanFactory; + } + + @SuppressWarnings("unchecked") + @Override + public T postProcess(T object) { + T result = (T) autowireBeanFactory.initializeBean(object, null); + if(result instanceof DisposableBean) { + disposableBeans.add((DisposableBean) result); + } + if(result instanceof Lifecycle) { + lifecycleBeans.add((Lifecycle) result); + } + return result; + } + + @Override + public void destroy() throws Exception { + for(DisposableBean disposable : disposableBeans) { + try { + disposable.destroy(); + } catch(Exception error) { + log.error(error); + } + } + } + + @Override + public void start() { + running = true; + for (Lifecycle bean : lifecycleBeans) { + bean.start(); + } + } + + @Override + public void stop() { + for (Lifecycle bean : lifecycleBeans) { + bean.stop(); + } + running = false; + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public int getPhase() { + return 0; + } + + @Override + public boolean isAutoStartup() { + return true; + } + + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java new file mode 100644 index 00000000..20a88a39 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configuration/ObjectPostProcessorConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configuration; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; + +/** + * Spring {@link Configuration} that exports the default + * {@link ObjectPostProcessor}. This class is not intended to be imported + * manually rather it is imported automatically when using {@link EnableAnnotationConfiguration} + * + * @author Rob Winch + * @author Janne Valkealahti + * + * @see EnableAnnotationConfiguration + */ +@Configuration +public class ObjectPostProcessorConfiguration { + + @Bean + public ObjectPostProcessor objectPostProcessor(AutowireCapableBeanFactory beanFactory) { + return new AutowireBeanFactoryObjectPostProcessor(beanFactory); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java new file mode 100644 index 00000000..4dc7a158 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultPropertiesConfigurer.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configurers; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +/** + * {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer AnnotationConfigurer} + * which knows how to handle configuring a {@link Properties}. + * + * @author Janne Valkealahti + * + * @param The Object being built by B + * @param The type of interface or builder itself returned by the configurer + * @param The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter} + */ +public class DefaultPropertiesConfigurer> + extends AnnotationConfigurerAdapter implements PropertiesConfigurer { + + private Properties properties = new Properties(); + + /** + * Adds a {@link Properties} to this builder. + * + * @param properties the properties + * @return the {@link PropertiesConfigurer} for chaining + */ + @Override + public PropertiesConfigurer properties(Properties properties) { + if (properties != null) { + this.properties.putAll(properties); + } + return this; + } + + @Override + public PropertiesConfigurer properties(Map properties) { + Properties props = new Properties(); + if (properties != null) { + props.putAll(properties); + } + return properties(props); + } + + /** + * Adds a property to this builder. + * + * @param key the key + * @param value the value + * @return the {@link PropertiesConfigurer} for chaining + */ + @Override + public PropertiesConfigurer property(String key, String value) { + properties.put(key, value); + return this; + } + + /** + * Gets the {@link Properties} configured for this builder. + * + * @return the properties + */ + public Properties getProperties() { + return properties; + } + + @Override + public void configure(B builder) throws Exception { + if (!configureProperties(builder, properties)) { + if (builder instanceof PropertiesConfigurerAware) { + ((PropertiesConfigurerAware)builder).configureProperties(properties); + } + } + } + + /** + * Configure properties. If this implementation is extended, + * custom configure handling can be handled here. + * + * @param builder the builder + * @param properties the properties + * @return true, if properties configure is handled + */ + protected boolean configureProperties(B builder, Properties properties){ + return false; + }; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java new file mode 100644 index 00000000..1ded2f00 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/DefaultResourceConfigurer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configurers; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +/** + * {@link AnnotationConfigurer} which knows how to handle + * configuring a {@link Resource}s. + * + * @author Janne Valkealahti + * + * @param The Object being built by B + * @param The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter} + * @param The type of an interface of B + */ +public class DefaultResourceConfigurer> + extends AnnotationConfigurerAdapter implements ResourceConfigurer { + + private Set resources = new HashSet(); + private final DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + + @Override + public void configure(B builder) throws Exception { + if (!configureResources(builder, resources)) { + if (builder instanceof ResourceConfigurerAware) { + ((ResourceConfigurerAware)builder).configureResources(resources); + } + } + } + + /** + * Adds a {@link Set} of {@link Resource}s to this builder. + * + * @param resources the resources + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resources(Set resources) { + this.resources.addAll(resources); + return this; + } + + /** + * Adds a {@link Resource} to this builder. + * + * @param resource the resource + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resource(Resource resource) { + resources.add(resource); + return this; + } + + /** + * Adds a {@link Resource} to this builder. + * + * @param resource the resource + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resource(String resource) { + resources.add(resourceLoader.getResource(resource)); + return this; + } + + /** + * Adds a {@link Resource}s to this builder. + * + * @param resources the resources + * @return the {@link ResourceConfigurer} for chaining + */ + @Override + public ResourceConfigurer resources(List resources) { + if (resources != null) { + for (String resource : resources) { + resource(resource); + } + } + return this; + } + + /** + * Gets the {@link Resource}s configured for this builder. + * + * @return the resources + */ + public Set getResources() { + return resources; + } + + /** + * Configure resources. If this implementation is extended, + * custom configure handling can be handled here. + * + * @param builder the builder + * @param resources the resources + * @return true, if resources configure is handled + */ + protected boolean configureResources(B builder, Set resources){ + return false; + }; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java new file mode 100644 index 00000000..e8ed6e79 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configurers; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +/** + * Interface for {@link DefaultPropertiesConfigurer} which act + * as intermediate gatekeeper between a user and + * an {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer}. + * + * @author Janne Valkealahti + * + * @param The parent return type of the configurer. + */ +public interface PropertiesConfigurer extends AnnotationConfigurerBuilder { + + PropertiesConfigurer properties(Properties properties); + + PropertiesConfigurer properties(Map properties); + + PropertiesConfigurer property(String key, String value); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java new file mode 100644 index 00000000..11bfeaa7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/PropertiesConfigurerAware.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configurers; + +import java.util.Properties; + +/** + * Interface for {@link org.springframework.statemachine.config.common.annotation.AnnotationBuilder AnnotationBuilder} + * which wants to be aware of {@link Properties} configured by {@link DefaultPropertiesConfigurer}. + * + * @author Janne Valkealahti + * + */ +public interface PropertiesConfigurerAware { + + /** + * Configure {@link Properties}. + * + * @param properties the properties + */ + void configureProperties(Properties properties); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java new file mode 100644 index 00000000..3bf28be3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configurers; + +import java.util.List; +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +/** + * Interface for {@link DefaultResourceConfigurer} which act + * as intermediate gatekeeper between a user and + * an {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer}. + * + * @author Janne Valkealahti + * + * @param The parent return type of the configurer. + */ +public interface ResourceConfigurer extends AnnotationConfigurerBuilder { + + ResourceConfigurer resources(Set resources); + + ResourceConfigurer resources(List resources); + + ResourceConfigurer resource(Resource resource); + + ResourceConfigurer resource(String resource); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java new file mode 100644 index 00000000..b52b196c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/common/annotation/configurers/ResourceConfigurerAware.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.configurers; + +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; + +/** + * Interface for {@link AnnotationBuilder} which wants to be + * aware of {@link Resource}s configured by {@link DefaultResourceConfigurer}. + * + * @author Janne Valkealahti + * + */ +public interface ResourceConfigurerAware { + + /** + * Configure {@link Resource}s. + * + * @param resources the resources + */ + void configureResources(Set resources); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java new file mode 100644 index 00000000..d201f06c --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java @@ -0,0 +1,103 @@ +package org.springframework.statemachine.config.configuration; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.state.State; + +@Configuration +public class StateMachineConfiguration, E extends Enum> extends + AbstractImportingAnnotationConfiguration, StateMachineConfig> { + + private final StateMachineConfigBuilder builder = new StateMachineConfigBuilder(); + + @Override + protected BeanDefinition buildBeanDefinition() throws Exception { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(StateMachineDelegatingFactoryBean.class); + beanDefinitionBuilder.addConstructorArgValue(builder); + return beanDefinitionBuilder.getBeanDefinition(); + } + + @Override + protected Class getAnnotation() { + return EnableStateMachine.class; + } + + private static class StateMachineDelegatingFactoryBean, E extends Enum> implements + FactoryBean, E>>, BeanFactoryAware, InitializingBean { + + private final StateMachineConfigBuilder builder; + + private List, StateMachineConfigBuilder>> configurers; + + private BeanFactory beanFactory; + + private StateMachine, E> stateMachine; + + @SuppressWarnings("unused") + public StateMachineDelegatingFactoryBean(StateMachineConfigBuilder builder) { + this.builder = builder; + } + + @Override + public StateMachine, E> getObject() throws Exception { + return stateMachine; + } + + @Override + public Class getObjectType() { + return StateMachine.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + for (AnnotationConfigurer, StateMachineConfigBuilder> configurer : configurers) { + builder.apply(configurer); + } + StateMachineConfig stateMachineConfig = builder.getOrBuild(); + StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); + StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + EnumStateMachineFactory stateMachineFactory = new EnumStateMachineFactory(stateMachineTransitions, stateMachineStates); + stateMachineFactory.setBeanFactory(beanFactory); + stateMachine = stateMachineFactory.getStateMachine(); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Autowired(required=false) + protected void onConfigurers( + List, StateMachineConfigBuilder>> configurers) + throws Exception { + this.configurers = configurers; + } + + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java new file mode 100644 index 00000000..13d84571 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java @@ -0,0 +1,103 @@ +package org.springframework.statemachine.config.configuration; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.EnumStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfig; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineConfigBuilder; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.state.State; + +@Configuration +public class StateMachineFactoryConfiguration, E extends Enum> extends + AbstractImportingAnnotationConfiguration, StateMachineConfig> { + + private final StateMachineConfigBuilder builder = new StateMachineConfigBuilder(); + + @Override + protected BeanDefinition buildBeanDefinition() throws Exception { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(StateMachineFactoryDelegatingFactoryBean.class); + beanDefinitionBuilder.addConstructorArgValue(builder); + return beanDefinitionBuilder.getBeanDefinition(); + } + + @Override + protected Class getAnnotation() { + return EnableStateMachineFactory.class; + } + + private static class StateMachineFactoryDelegatingFactoryBean, E extends Enum> implements + FactoryBean, E>>, BeanFactoryAware, InitializingBean { + + private final StateMachineConfigBuilder builder; + + private List, StateMachineConfigBuilder>> configurers; + + private BeanFactory beanFactory; + + private StateMachineFactory, E> stateMachineFactory; + + @SuppressWarnings("unused") + public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder builder) { + this.builder = builder; + } + + @Override + public StateMachineFactory, E> getObject() throws Exception { + return stateMachineFactory; + } + + @Override + public Class getObjectType() { + return StateMachineFactory.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + for (AnnotationConfigurer, StateMachineConfigBuilder> configurer : configurers) { + builder.apply(configurer); + } + StateMachineConfig stateMachineConfig = builder.getOrBuild(); + StateMachineTransitions stateMachineTransitions = stateMachineConfig.getTransitions(); + StateMachineStates stateMachineStates = stateMachineConfig.getStates(); + EnumStateMachineFactory enumStateMachineFactory = new EnumStateMachineFactory(stateMachineTransitions, stateMachineStates); + enumStateMachineFactory.setBeanFactory(beanFactory); + this.stateMachineFactory = enumStateMachineFactory; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Autowired(required=false) + protected void onConfigurers( + List, StateMachineConfigBuilder>> configurers) + throws Exception { + this.configurers = configurers; + } + + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java new file mode 100644 index 00000000..baaa93fd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * Default implementation of a {@link ExternalTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultExternalTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements ExternalTransitionConfigurer { + + private S source; + + private S target; + + private E event; + + private Collection actions = new ArrayList(); + + private Guard guard; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.add(source, target, event, actions, guard, TransitionKind.EXTERNAL); + } + + @Override + public ExternalTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public ExternalTransitionConfigurer target(S target) { + this.target = target; + return this; + } + + @Override + public ExternalTransitionConfigurer event(E event) { + this.event = event; + return this; + } + + @Override + public ExternalTransitionConfigurer action(Action action) { + actions.add(action); + return this; + } + + @Override + public ExternalTransitionConfigurer guard(Guard guard) { + this.guard = guard; + return this; + } + + @Override + public ExternalTransitionConfigurer guardExpression(String expression) { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java new file mode 100644 index 00000000..c976f825 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * Default implementation of a {@link InternalTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultInternalTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements InternalTransitionConfigurer { + + private S source; + + private S target; + + private E event; + + private Collection actions = new ArrayList(); + + private Guard guard; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.add(source, target, event, actions, guard, TransitionKind.INTERNAL); + } + + @Override + public InternalTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public InternalTransitionConfigurer event(E event) { + this.event = event; + return this; + } + + @Override + public InternalTransitionConfigurer action(Action action) { + actions.add(action); + return this; + } + + @Override + public InternalTransitionConfigurer guard(Guard guard) { + this.guard = guard; + return this; + } + + @Override + public InternalTransitionConfigurer guardExpression(String expression) { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java new file mode 100644 index 00000000..a8179e97 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineStateBuilder; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStates; +import org.springframework.statemachine.config.builders.StateMachineStates.StateData; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; + +public class DefaultStateConfigurer + extends AnnotationConfigurerAdapter, StateMachineStateConfigurer, StateMachineStateBuilder> + implements StateConfigurer { + + private final Collection> states = new ArrayList>(); + + private S initial; + + @Override + public void configure(StateMachineStateBuilder builder) throws Exception { + builder.add(states); + builder.setInitialState(initial); + } + + @Override + public StateConfigurer initial(S initial) { + this.initial = initial; + return this; + } + + @Override + public StateConfigurer state(S state) { + return state(state, (E[])null); + } + + @Override + public StateConfigurer state(S state, Collection entryActions, Collection exitActions) { + states.add(new StateData(state, null, entryActions, exitActions)); + return this; + } + + @Override + public StateConfigurer state(S state, E... deferred) { + states.add(new StateData(state, deferred)); + return this; + } + + @Override + public StateConfigurer states(Set states) { + for (S s : states) { + state(s); + } + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java new file mode 100644 index 00000000..9ae8df40 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/ExternalTransitionConfigurer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring external {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface ExternalTransitionConfigurer extends + TransitionConfigurer, S, E> { + + /** + * Specify a target state {@code S} for this {@link Transition}. + * + * @param target the target state {@code S} + * @return configurer for chaining + */ + ExternalTransitionConfigurer target(S target); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java new file mode 100644 index 00000000..01410ed5 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/InternalTransitionConfigurer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring internal {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface InternalTransitionConfigurer extends + TransitionConfigurer, S, E> { + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java new file mode 100644 index 00000000..366ed70f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import java.util.Collection; +import java.util.Set; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; + +public interface StateConfigurer extends + AnnotationConfigurerBuilder> { + + StateConfigurer initial(S initial); + + StateConfigurer state(S state); + + StateConfigurer state(S state, Collection entryActions, Collection exitActions); + + StateConfigurer state(S state, E... deferred); + + StateConfigurer states(Set states); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java new file mode 100644 index 00000000..01690fbd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 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 + * + * http://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.config.configurers; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.transition.Transition; + +/** + * Base {@code TransitionConfigurer} interface for configuring {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of a transition configurer + * @param the type of state + * @param the type of event + */ +public interface TransitionConfigurer extends + AnnotationConfigurerBuilder> { + + /** + * Specify a source state {@code S} for this {@link Transition}. + * + * @param source the source state {@code S} + * @return configurer for chaining + */ + T source(S source); + + /** + * Specify event {@code E} for this {@link Transition}. + * + * @param event the event for transition + * @return configurer for chaining + */ + T event(E event); + + /** + * Specify {@link Action} for this {@link Transition}. + * + * @param action the action + * @return configurer for chaining + */ + T action(Action action); + + /** + * Specify a {@link Guard} for this {@link Transition}. + * + * @param guard the guard + * @return configurer for chaining + */ + T guard(Guard guard); + + /** + * Specify a {@link Guard} backed by a SpEL expression for this {@link Transition}. + * + * @param expression the SpEL expression + * @return configurer for chaining + */ + T guardExpression(String expression); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java new file mode 100644 index 00000000..b0477733 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 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 + * + * http://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.guard; + +import org.springframework.statemachine.StateContext; + +/** + * {@code Guard}s are typically considered as guard conditions which affect the + * behaviour of a state machine by enabling actions or transitions only when they + * evaluate to {@code TRUE} and disabling them when they evaluate to + * {@code FALSE}. + * + * @author Janne Valkealahti + * + */ +public interface Guard { + + /** + * Evaluate a guard condition. + * + * @param context the state context + * @return true, if guard evaluation is successful, false otherwise. + */ + boolean evaluate(StateContext context); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java new file mode 100644 index 00000000..7b731ab3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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 + * + * http://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.guard; + +import org.springframework.expression.Expression; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.statemachine.StateContext; +import org.springframework.util.Assert; + +/** + * {@link Guard} which uses Spring SpEL expression for condition evaluation. + * + * @author Janne Valkealahti + * + */ +public class SpelExpressionGuard implements Guard { + + private final Expression expression; + + /** + * Instantiates a new spel expression guard. + * + * @param expression the expression + */ + public SpelExpressionGuard(Expression expression) { + Assert.notNull(expression, "Expression cannot be null"); + this.expression = expression; + } + + @Override + public boolean evaluate(StateContext context) { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(context); + return expression.getValue(evaluationContext, Boolean.class); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java new file mode 100644 index 00000000..ec553566 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/AbstractCompositeListener.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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 + * + * http://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.listener; + +import java.util.List; + +/** + * Base implementation for all composite listeners. + * + * @author Janne Valkealahti + * + * @param the type of the listener + */ +public class AbstractCompositeListener { + + /** List of ordered composite listeners */ + private OrderedComposite listeners; + + /** + * Constructs instance with an empty listener list. + */ + public AbstractCompositeListener() { + listeners = new OrderedComposite(); + } + + /** + * Sets the list of listeners. This clears + * all existing listeners. + * + * @param listeners the new listeners + */ + public void setListeners(List listeners) { + this.listeners.setItems(listeners); + } + + /** + * Register a new listener. + * + * @param listener the listener + */ + public void register(T listener) { + listeners.add(listener); + } + + /** + * Gets the listeners. + * + * @return the listeners + */ + public OrderedComposite getListeners() { + return listeners; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java new file mode 100644 index 00000000..263da314 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 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 + * + * http://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.listener; + +import java.util.Iterator; + +import org.springframework.statemachine.state.State; + +public class CompositeStateMachineListener extends AbstractCompositeListener, E>> implements + StateMachineListener, E> { + + @Override + public void stateChanged(State from, State to) { + for (Iterator, E>> iterator = getListeners().reverse(); iterator.hasNext();) { + StateMachineListener, E> listener = iterator.next(); + listener.stateChanged(from, to); + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java new file mode 100644 index 00000000..b94c9660 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/OrderedComposite.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015 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 + * + * http://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.listener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; + +/** + * Composite item which can be used in other components which + * may want to allow automatic and annotation based ordering. + * Good use case is a list of listeners where user may want + * to place some of them to be processed before the others. + * + * @author Janne Valkealahti + * + * @param the type of the item + */ +public class OrderedComposite { + + private List unordered = new ArrayList(); + + private List ordered = new ArrayList(); + + private Comparator comparator = new AnnotationAwareOrderComparator(); + + private List list = new ArrayList(); + + /** + * Public setter for the listeners. + * + * @param items items + */ + public void setItems(List items) { + unordered.clear(); + ordered.clear(); + for (S s : items) { + add(s); + } + } + + /** + * Register additional item. + * + * @param item item + */ + public void add(S item) { + if (item instanceof Ordered) { + if (!ordered.contains(item)) { + ordered.add(item); + } + } else if (AnnotationUtils.isAnnotationDeclaredLocally(Order.class, item.getClass())) { + if (!ordered.contains(item)) { + ordered.add(item); + } + } else if (!unordered.contains(item)) { + unordered.add(item); + } + Collections.sort(ordered, comparator); + list.clear(); + list.addAll(ordered); + list.addAll(unordered); + } + + /** + * Public getter for the list of items. The {@link Ordered} items come + * first, followed by any unordered ones. + * + * @return an iterator over the list of items + */ + public Iterator iterator() { + return new ArrayList(list).iterator(); + } + + /** + * Public getter for the list of items in reverse. The {@link Ordered} items + * come last, after any unordered ones. + * + * @return an iterator over the list of items + */ + public Iterator reverse() { + ArrayList result = new ArrayList(list); + Collections.reverse(result); + return result.iterator(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java new file mode 100644 index 00000000..eec8ffe6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 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 + * + * http://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.listener; + +public interface StateMachineListener { + + void stateChanged(S from, S to); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java new file mode 100644 index 00000000..1412c072 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodAnnotationPostProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * Strategy interface for post-processing annotated methods. + * + * @author Mark Fisher + * @author Janne Valkealahti + * + * @param the type of an annotation + */ +public interface MethodAnnotationPostProcessor { + + /** + * Post process a bean. As a result of a given bean, its name, method and + * annotation in a method, this method can return a new bean or + * null. Caller of this method is then responsible to handle + * newly created object. + * + * @param bean the bean + * @param beanName the bean name + * @param method the method + * @param annotation the annotation + * @return the object + */ + Object postProcess(Object bean, String beanName, Method method, T annotation); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java new file mode 100644 index 00000000..37261fd4 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * A simple {@link StateMachineRuntimeProcessor} implementation using + * methods from a state machine protected bean. + * + * @author Janne Valkealahti + * + * @param the return type + */ +public class MethodInvokingStateMachineRuntimeProcessor implements StateMachineRuntimeProcessor { + + private final StateMachineMethodInvokerHelper delegate; + + public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Method method) { + delegate = new StateMachineMethodInvokerHelper(targetObject, method); + } + + public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, String methodName) { + delegate = new StateMachineMethodInvokerHelper(targetObject, methodName); + } + + public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Class annotationType) { + delegate = new StateMachineMethodInvokerHelper(targetObject, annotationType); + } + + @Override + public T process(StateMachineRuntime stateMachineRuntime) { + try { + return delegate.process(stateMachineRuntime); + } catch (Exception e) { + throw new RuntimeException("Error processing bean", e); + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java new file mode 100644 index 00000000..f6055e7a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineActivatorAnnotationPostProcessor.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.reflect.Method; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.core.annotation.OrderUtils; +import org.springframework.statemachine.annotation.OnTransition; + +/** + * Post-processor for Methods annotated with {@link OnTransition}. + * + * @author Janne Valkealahti + * + */ +public class StateMachineActivatorAnnotationPostProcessor implements MethodAnnotationPostProcessor{ + + protected final BeanFactory beanFactory; + + public StateMachineActivatorAnnotationPostProcessor(ListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcess(Object bean, String beanName, Method method, OnTransition annotation) { + StateMachineHandler handler = new StateMachineOnTransitionHandler(bean, method, annotation); + + Integer order = findOrder(bean, method); + if (order != null) { + handler.setOrder(order); + } + return handler; + } + + /** + * Find {@link Order} order either from a class or + * method level. Method level always takes presence + * over class level. + * + * @param bean the bean to inspect + * @param method the method to inspect + * @return the order or NULL if not found + */ + private static Integer findOrder(Object bean, Method method) { + Integer order = OrderUtils.getOrder(bean.getClass()); + Order ann = AnnotationUtils.findAnnotation(method, Order.class); + if (ann != null) { + order = ann.value(); + } + return order; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java new file mode 100644 index 00000000..ea717034 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineAnnotationPostProcessor.java @@ -0,0 +1,248 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * A {@link BeanPostProcessor} implementation that processes method-level + * annotations such as {@link OnTransition}. + * + * @author Mark Fisher + * @author Marius Bogoevici + * @author Janne Valkealahti + * + */ +public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, + Lifecycle, ApplicationListener { + + private final static Log log = LogFactory.getLog(StateMachineAnnotationPostProcessor.class); + + /** Factory from BeanFactoryAware */ + private volatile ConfigurableListableBeanFactory beanFactory; + + /** Post processors map - annotation -> method post processor */ + private final Map, MethodAnnotationPostProcessor> postProcessors = + new HashMap, MethodAnnotationPostProcessor>(); + + /** + * Application events for post processed beans (if bean instance of ApplicationListener) will + * be dispatched from here via callback in this class. + */ + private final Set> listeners = new HashSet>(); + + /** + * Lifecycle callbacks for post processed bean (if bean instance of Lifecycle) will + * be dispatched from here via callback in this class. + */ + private final Set lifecycles = new HashSet(); + + /** Flag for Lifecycle in this class */ + private volatile boolean running = true; + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(), + "a ConfigurableListableBeanFactory is required"); + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + @Override + public void afterPropertiesSet() { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + postProcessors.put(OnTransition.class, new StateMachineActivatorAnnotationPostProcessor(beanFactory)); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + final Class beanClass = getBeanClass(bean); + + if (!isStereotype(beanClass)) { + // we only post-process stereotype components + return bean; + } + + ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Annotation[] annotations = AnnotationUtils.getAnnotations(method); + + for (Annotation annotation : annotations) { + MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType()); + + if (postProcessor != null && shouldCreateHandler(annotation)) { + Object result = postProcessor.postProcess(bean, beanName, method, annotation); + + if (result != null && result instanceof StateMachineHandler) { + String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType()); + + if (result instanceof BeanNameAware) { + ((BeanNameAware) result).setBeanName(endpointBeanName); + } + beanFactory.registerSingleton(endpointBeanName, result); + if (result instanceof BeanFactoryAware) { + ((BeanFactoryAware) result).setBeanFactory(beanFactory); + } + if (result instanceof InitializingBean) { + try { + ((InitializingBean) result).afterPropertiesSet(); + } catch (Exception e) { + throw new BeanInitializationException("failed to initialize annotated component", e); + } + } + if (result instanceof Lifecycle) { + lifecycles.add((Lifecycle) result); + if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) { + ((SmartLifecycle) result).start(); + } + } + if (result instanceof ApplicationListener) { + listeners.add((ApplicationListener) result); + } + } + } + } + } + }); + return bean; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + for (ApplicationListener listener : listeners) { + try { + listener.onApplicationEvent(event); + } catch (ClassCastException e) { + if (log.isWarnEnabled() && event != null) { + log.warn("ApplicationEvent of type [" + event.getClass() + + "] not accepted by ApplicationListener [" + listener + "]"); + } + } + } + } + + @Override + public boolean isRunning() { + return this.running; + } + + @Override + public void start() { + for (Lifecycle lifecycle : this.lifecycles) { + if (!lifecycle.isRunning()) { + lifecycle.start(); + } + } + this.running = true; + } + + @Override + public void stop() { + for (Lifecycle lifecycle : this.lifecycles) { + if (lifecycle.isRunning()) { + lifecycle.stop(); + } + } + this.running = false; + } + + private boolean shouldCreateHandler(Annotation annotation) { + return true; + } + + /** + * Gets the bean class. Will check if bean is a proxy and + * find a class from there as target class, otherwise + * we just get bean class. + * + * @param bean the bean + * @return the bean class + */ + private Class getBeanClass(Object bean) { + Class targetClass = AopUtils.getTargetClass(bean); + return (targetClass != null) ? targetClass : bean.getClass(); + } + + /** + * Checks if class is a stereotype meaning if there is + * a Component annotation present. + * + * @param beanClass the bean class + * @return true, if is stereotype + */ + private boolean isStereotype(Class beanClass) { + List annotations = new ArrayList(Arrays.asList(beanClass.getAnnotations())); + Class[] interfaces = beanClass.getInterfaces(); + for (Class iface : interfaces) { + annotations.addAll(Arrays.asList(iface.getAnnotations())); + } + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (annotationType.equals(Component.class) || annotationType.isAnnotationPresent(Component.class)) { + return true; + } + } + return false; + } + + private String generateBeanName(String originalBeanName, Method method, Class annotationType) { + String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType); + String name = baseName; + int count = 1; + while (beanFactory.containsBean(name)) { + name = baseName + "#" + (++count); + } + return name; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java new file mode 100644 index 00000000..df31233b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.reflect.Method; + +import org.springframework.core.Ordered; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.statemachine.annotation.WithStateMachine; + +/** + * Handler for a common object representing something to be run. + * This is usually used when a plain pojo is configured with {@link WithStateMachine} + * and {@link OnTransition} annotations. + * + * @author Janne Valkealahti + * + */ +public class StateMachineHandler implements Ordered { + + private final StateMachineRuntimeProcessor processor; + + private int order = Ordered.LOWEST_PRECEDENCE; + + /** + * Instantiates a new container handler. + * + * @param target the target bean + */ +// public StateMachineHandler(Object target) { +// this(new MethodInvokingStateMachineRuntimeProcessor(target, OnTransition.class)); +// } + + /** + * Instantiates a new container handler. + * + * @param target the target bean + * @param method the method + */ + public StateMachineHandler(Object target, Method method) { + this(new MethodInvokingStateMachineRuntimeProcessor(target, method)); + } + + /** + * Instantiates a new container handler. + * + * @param target the target bean + * @param methodName the method name + */ + public StateMachineHandler(Object target, String methodName) { + this(new MethodInvokingStateMachineRuntimeProcessor(target, methodName)); + } + + /** + * Instantiates a new container handler. + * + * @param the generic type + * @param processor the processor + */ + public StateMachineHandler(MethodInvokingStateMachineRuntimeProcessor processor) { + this.processor = processor; + } + + @Override + public int getOrder() { + return order; + } + + /** + * Sets the order used get value from {@link #getOrder()}. + * Default value is {@link Ordered#LOWEST_PRECEDENCE}. + * + * @param order the new order + */ + public void setOrder(int order) { + this.order = order; + } + + /** + * Handle container using a {@link StateMachineRuntimeProcessor}. + * + * @param stateMachineRuntime the state machine runtime + * @return the result value + */ + public Object handle(StateMachineRuntime stateMachineRuntime) { + return processor.process(stateMachineRuntime); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java new file mode 100644 index 00000000..f6f1e830 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineMethodInvokerHelper.java @@ -0,0 +1,553 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.support.AbstractExpressionEvaluator; +import org.springframework.statemachine.support.AnnotatedMethodFilter; +import org.springframework.statemachine.support.FixedMethodFilter; +import org.springframework.statemachine.support.UniqueMethodFilter; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.ReflectionUtils.MethodFilter; + +/** + * A helper class using spel to execute target methods. + * + * @author Janne Valkealahti + * + * @param the return type + */ +public class StateMachineMethodInvokerHelper extends AbstractExpressionEvaluator { + + private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS"; + + private static final String CANDIDATE_MESSAGE_METHODS = "CANDIDATE_MESSAGE_METHODS"; + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final Object targetObject; + + private volatile String displayString; + + private volatile boolean requiresReply; + + private final Map, HandlerMethod> handlerMethods; + + private final Map, HandlerMethod> handlerMessageMethods; + + private final LinkedList, HandlerMethod>> handlerMethodsList; + + private final HandlerMethod handlerMethod; + + private final Class expectedType; + + public StateMachineMethodInvokerHelper(Object targetObject, Method method) { + this(targetObject, method, null); + } + + public StateMachineMethodInvokerHelper(Object targetObject, Method method, Class expectedType) { + this(targetObject, null, method, expectedType); + } + + public StateMachineMethodInvokerHelper(Object targetObject, String methodName) { + this(targetObject, methodName, null); + } + + public StateMachineMethodInvokerHelper(Object targetObject, String methodName, Class expectedType) { + this(targetObject, null, methodName, expectedType); + } + + public StateMachineMethodInvokerHelper(Object targetObject, Class annotationType) { + this(targetObject, annotationType, null); + } + + public StateMachineMethodInvokerHelper(Object targetObject, Class annotationType, + Class expectedType) { + this(targetObject, annotationType, (String) null, expectedType); + } + + public T process(StateMachineRuntime stateMachineRuntime) throws Exception { + ParametersWrapper wrapper = new ParametersWrapper(stateMachineRuntime.getStateContext()); + return processInternal(wrapper); + } + + @Override + public String toString() { + return this.displayString; + } + + private StateMachineMethodInvokerHelper(Object targetObject, Class annotationType, + Method method, Class expectedType) { + Assert.notNull(method, "method must not be null"); + this.expectedType = expectedType; + this.requiresReply = expectedType != null; + if (expectedType != null) { + Assert.isTrue(method.getReturnType() != Void.class && method.getReturnType() != Void.TYPE, + "method must have a return type"); + } + Assert.notNull(targetObject, "targetObject must not be null"); + this.targetObject = targetObject; + this.handlerMethod = new HandlerMethod(method); + this.handlerMethods = null; + this.handlerMessageMethods = null; + this.handlerMethodsList = null; + this.prepareEvaluationContext(this.getEvaluationContext(false), method, annotationType); + this.setDisplayString(targetObject, method); + } + + private StateMachineMethodInvokerHelper(Object targetObject, Class annotationType, + String methodName, Class expectedType) { + Assert.notNull(targetObject, "targetObject must not be null"); + this.expectedType = expectedType; + this.targetObject = targetObject; + this.requiresReply = expectedType != null; + Map, HandlerMethod>> handlerMethodsForTarget = this.findHandlerMethodsForTarget( + targetObject, annotationType, methodName, requiresReply); + Map, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS); + Map, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS); + if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty()) + || (handlerMessageMethods.size() == 1 && handlerMethods.isEmpty())) { + if (handlerMethods.size() == 1) { + this.handlerMethod = handlerMethods.values().iterator().next(); + } else { + this.handlerMethod = handlerMessageMethods.values().iterator().next(); + } + this.handlerMethods = null; + this.handlerMessageMethods = null; + this.handlerMethodsList = null; + } else { + this.handlerMethod = null; + this.handlerMethods = handlerMethods; + this.handlerMessageMethods = handlerMessageMethods; + this.handlerMethodsList = new LinkedList, HandlerMethod>>(); + + // TODO Consider to use global option to determine a precedence of + // methods + this.handlerMethodsList.add(this.handlerMethods); + this.handlerMethodsList.add(this.handlerMessageMethods); + } + this.prepareEvaluationContext(this.getEvaluationContext(false), methodName, annotationType); + this.setDisplayString(targetObject, methodName); + } + + private void setDisplayString(Object targetObject, Object targetMethod) { + StringBuilder sb = new StringBuilder(targetObject.getClass().getName()); + if (targetMethod instanceof Method) { + sb.append("." + ((Method) targetMethod).getName()); + } else if (targetMethod instanceof String) { + sb.append("." + targetMethod); + } + this.displayString = sb.toString() + "]"; + } + + private void prepareEvaluationContext(StandardEvaluationContext context, Object method, + Class annotationType) { + Class targetType = AopUtils.getTargetClass(this.targetObject); + if (method instanceof Method) { + context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method)); + if (expectedType != null) { + Assert.state( + context.getTypeConverter().canConvert( + TypeDescriptor.valueOf(((Method) method).getReturnType()), + TypeDescriptor.valueOf(expectedType)), "Cannot convert to expected type (" + + expectedType + ") from " + method); + } + } else if (method == null || method instanceof String) { + AnnotatedMethodFilter filter = new AnnotatedMethodFilter(annotationType, (String) method, + this.requiresReply); + Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()), + "Cannot convert to expected type (" + expectedType + ") from " + method); + context.registerMethodFilter(targetType, filter); + } + context.setVariable("target", targetObject); + } + + private boolean canReturnExpectedType(AnnotatedMethodFilter filter, Class targetType, TypeConverter typeConverter) { + if (expectedType == null) { + return true; + } + List methods = filter.filter(Arrays.asList(ReflectionUtils.getAllDeclaredMethods(targetType))); + for (Method method : methods) { + if (typeConverter.canConvert(TypeDescriptor.valueOf(method.getReturnType()), TypeDescriptor.valueOf(expectedType))) { + return true; + } + } + return false; + } + + private T processInternal(ParametersWrapper parameters) throws Exception { + HandlerMethod candidate = this.findHandlerMethodForParameters(parameters); + Assert.notNull(candidate, "No candidate methods found for messages."); + Expression expression = candidate.getExpression(); + Class expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType(); + try { + @SuppressWarnings("unchecked") + T result = (T) this.evaluateExpression(expression, parameters, expectedType); + if (this.requiresReply) { + Assert.notNull(result, "Expression evaluation result was null, but this processor requires a reply."); + } + return result; + } catch (Exception e) { + Throwable evaluationException = e; + if (e instanceof EvaluationException && e.getCause() != null) { + evaluationException = e.getCause(); + } + if (evaluationException instanceof Exception) { + throw (Exception) evaluationException; + } else { + throw new IllegalStateException("Cannot process message", evaluationException); + } + } + } + + private Map, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject, + final Class annotationType, final String methodName, final boolean requiresReply) { + + Map, HandlerMethod>> handlerMethods = new HashMap, HandlerMethod>>(); + + final Map, HandlerMethod> candidateMethods = new HashMap, HandlerMethod>(); + final Map, HandlerMethod> candidateMessageMethods = new HashMap, HandlerMethod>(); + final Class targetClass = this.getTargetClass(targetObject); + MethodFilter methodFilter = new UniqueMethodFilter(targetClass); + ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + boolean matchesAnnotation = false; + if (method.isBridge()) { + return; + } + if (isMethodDefinedOnObjectClass(method)) { + return; + } + if (method.getDeclaringClass().equals(Proxy.class)) { + return; + } + if (!Modifier.isPublic(method.getModifiers())) { + return; + } + if (requiresReply && void.class.equals(method.getReturnType())) { + return; + } + if (methodName != null && !methodName.equals(method.getName())) { + return; + } + if (annotationType != null && AnnotationUtils.findAnnotation(method, annotationType) != null) { + matchesAnnotation = true; + } + HandlerMethod handlerMethod = null; + try { + handlerMethod = new HandlerMethod(method); + } + catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Method [" + method + "] is not eligible for container handling.", e); + } + return; + } + Class targetParameterType = handlerMethod.getTargetParameterType(); + if (matchesAnnotation || annotationType == null) { + if (handlerMethod.isMessageMethod()) { + if (candidateMessageMethods.containsKey(targetParameterType)) { + throw new IllegalArgumentException("Found more than one method match for type " + + "[Message<" + targetParameterType + ">]"); + } + candidateMessageMethods.put(targetParameterType, handlerMethod); + } else { + if (candidateMethods.containsKey(targetParameterType)) { + String exceptionMessage = "Found more than one method match for "; + if (Void.class.equals(targetParameterType)) { + exceptionMessage += "empty parameter for 'payload'"; + } else { + exceptionMessage += "type [" + targetParameterType + "]"; + } + throw new IllegalArgumentException(exceptionMessage); + } + candidateMethods.put(targetParameterType, handlerMethod); + } + } + } + }, methodFilter); + + if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) { + handlerMethods.put(CANDIDATE_METHODS, candidateMethods); + handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods); + return handlerMethods; + } + + Assert.state(!handlerMethods.isEmpty(), "Target object of type [" + this.targetObject.getClass() + + "] has no eligible methods for handling Container."); + + return handlerMethods; + } + + private Class getTargetClass(Object targetObject) { + Class targetClass = targetObject.getClass(); + if (AopUtils.isAopProxy(targetObject)) { + targetClass = AopUtils.getTargetClass(targetObject); + if (targetClass == targetObject.getClass()) { + try { + // Maybe a proxy with no target - e.g. gateway + Class[] interfaces = ((Advised) targetObject).getProxiedInterfaces(); + if (interfaces != null && interfaces.length == 1) { + targetClass = interfaces[0]; + } + } + catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Exception trying to extract interface", e); + } + } + } + } + else if (org.springframework.util.ClassUtils.isCglibProxyClass(targetClass)) { + Class superClass = targetObject.getClass().getSuperclass(); + if (!Object.class.equals(superClass)) { + targetClass = superClass; + } + } + return targetClass; + } + + private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) { + if (this.handlerMethod != null) { + return this.handlerMethod; + } else { + return this.handlerMethods.get(Void.class); + } + } + + private static boolean isMethodDefinedOnObjectClass(Method method) { + if (method == null) { + return false; + } + if (method.getDeclaringClass().equals(Object.class)) { + return true; + } + if (ReflectionUtils.isEqualsMethod(method) || ReflectionUtils.isHashCodeMethod(method) + || ReflectionUtils.isToStringMethod(method) || AopUtils.isFinalizeMethod(method)) { + return true; + } + return (method.getName().equals("clone") && method.getParameterTypes().length == 0); + } + + + /** + * Helper class for generating and exposing metadata for a candidate handler method. The metadata includes the SpEL + * expression and the expected payload type. + */ + private static class HandlerMethod { + + private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); + + private final Method method; + + private final Expression expression; + + private volatile TypeDescriptor targetParameterTypeDescriptor; + + private volatile Class targetParameterType = Void.class; + + private volatile boolean messageMethod; + + HandlerMethod(Method method) { + this.method = method; + this.expression = this.generateExpression(method); + } + + + Expression getExpression() { + return this.expression; + } + + Class getTargetParameterType() { + return this.targetParameterType; + } + + private boolean isMessageMethod() { + return messageMethod; + } + + @Override + public String toString() { + return this.method.toString(); + } + + private Expression generateExpression(Method method) { + StringBuilder sb = new StringBuilder("#target." + method.getName() + "("); + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + boolean hasUnqualifiedMapParameter = false; + for (int i = 0; i < parameterTypes.length; i++) { + if (i != 0) { + sb.append(", "); + } + MethodParameter methodParameter = new MethodParameter(method, i); + TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(methodParameter); + Class parameterType = parameterTypeDescriptor.getObjectType(); + Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]); + if (mappingAnnotation != null) { + Class annotationType = mappingAnnotation.annotationType(); + +// if (annotationType.equals(YarnEnvironments.class)) { +// sb.append("environment"); +// } else if (annotationType.equals(YarnEnvironment.class)) { +// YarnEnvironment headerAnnotation = (YarnEnvironment) mappingAnnotation; +// sb.append(this.determineEnvironmentExpression(headerAnnotation, methodParameter)); +// } else if (annotationType.equals(YarnParameters.class)) { +// Assert.isTrue(Map.class.isAssignableFrom(parameterType), +// "The @YarnParameters annotation can only be applied to a Map-typed parameter."); +// sb.append("parameters"); +// } else if (annotationType.equals(YarnParameter.class)) { +// YarnParameter headerAnnotation = (YarnParameter) mappingAnnotation; +// sb.append(this.determineParameterExpression(headerAnnotation, methodParameter)); +// } + } + } + if (hasUnqualifiedMapParameter) { + if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType)) { + throw new IllegalArgumentException( + "Unable to determine payload matching parameter due to ambiguous Map typed parameters. " + + "Consider adding the @Payload and or @Headers annotations as appropriate."); + } + } + sb.append(")"); + if (this.targetParameterTypeDescriptor == null) { + this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class); + } + return EXPRESSION_PARSER.parseExpression(sb.toString()); + } + + private Annotation findMappingAnnotation(Annotation[] annotations) { + if (annotations == null || annotations.length == 0) { + return null; + } + Annotation match = null; + for (Annotation annotation : annotations) { + Class type = annotation.annotationType(); +// if (type.equals(YarnParameters.class) || type.equals(YarnParameter.class) +// || type.equals(YarnEnvironments.class) || type.equals(YarnEnvironment.class)) { +// if (match != null) { +// throw new IllegalArgumentException( +// "At most one parameter annotation can be provided for message mapping, " +// + "but found two: [" + match.annotationType().getName() + "] and [" +// + annotation.annotationType().getName() + "]"); +// } +// match = annotation; +// } + } + return match; + } + +// private String determineParameterExpression(YarnParameter parameterAnnotation, MethodParameter methodParameter) { +// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); +// String headerName = null; +// String relativeExpression = ""; +// String valueAttribute = parameterAnnotation.value(); +// if (!StringUtils.hasText(valueAttribute)) { +// headerName = methodParameter.getParameterName(); +// } else if (valueAttribute.indexOf('.') != -1) { +// String tokens[] = valueAttribute.split("\\.", 2); +// headerName = tokens[0]; +// if (StringUtils.hasText(tokens[1])) { +// relativeExpression = "." + tokens[1]; +// } +// } else { +// headerName = valueAttribute; +// } +// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is " +// + "disabled or header name is not explicitly provided via @YarnParameter annotation."); +// String headerRetrievalExpression = "parameters['" + headerName + "']"; +// String fullHeaderExpression = headerRetrievalExpression + relativeExpression; +// String fallbackExpression = (parameterAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: " +// + headerName + "')" +// : "null"; +// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; +// } + +// private String determineEnvironmentExpression(YarnEnvironment environmentAnnotation, MethodParameter methodParameter) { +// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER); +// String headerName = null; +// String relativeExpression = ""; +// String valueAttribute = environmentAnnotation.value(); +// if (!StringUtils.hasText(valueAttribute)) { +// headerName = methodParameter.getParameterName(); +// } else if (valueAttribute.indexOf('.') != -1) { +// String tokens[] = valueAttribute.split("\\.", 2); +// headerName = tokens[0]; +// if (StringUtils.hasText(tokens[1])) { +// relativeExpression = "." + tokens[1]; +// } +// } else { +// headerName = valueAttribute; +// } +// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is " +// + "disabled or header name is not explicitly provided via @YarnEnvironment annotation."); +// String headerRetrievalExpression = "environment['" + headerName + "']"; +// String fullHeaderExpression = headerRetrievalExpression + relativeExpression; +// String fallbackExpression = (environmentAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: " +// + headerName + "')" +// : "null"; +// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression; +// } + + } + + /** + * Wrapping everything we need to work with spel. + */ + public class ParametersWrapper { + + private final StateContext stateContext; + + public ParametersWrapper(StateContext stateContext) { + this.stateContext = stateContext; + } + + public StateContext getStateContext() { + return stateContext; + } + + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java new file mode 100644 index 00000000..ed45aa8a --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineOnTransitionHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import java.lang.reflect.Method; + +import org.springframework.statemachine.annotation.OnTransition; + +public class StateMachineOnTransitionHandler extends StateMachineHandler { + + private OnTransition annotation; + + public StateMachineOnTransitionHandler(Object target, Method method, OnTransition annotation) { + super(target, method); + this.annotation = annotation; + } + + public OnTransition getAnnotation() { + return annotation; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java new file mode 100644 index 00000000..3ac4be7f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntime.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +import org.springframework.statemachine.StateContext; + +/** + * A generic runtime representation of a state machine. + * + * @author Janne Valkealahti + * + */ +public interface StateMachineRuntime { + + StateContext getStateContext(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java new file mode 100644 index 00000000..10a42e8f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineRuntimeProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 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 + * + * http://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.processor; + +/** + * Defines a strategy of processing a state machine and returning + * some Object (or null). + * + * @author Janne Valkealahti + * + * @param type + */ +public interface StateMachineRuntimeProcessor { + + /** + * Process the container based on information available + * from {@link StateMachineRuntime}. + * + * @param stateMachineRuntime the yarn container runtime + * @return the result + */ + T process(StateMachineRuntime stateMachineRuntime); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java new file mode 100644 index 00000000..c17f4daf --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 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 + * + * http://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.state; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +public abstract class AbstractState implements State { + + private S id; + private Collection deferred; + private Collection entryActions; + private Collection exitActions; + + public AbstractState(S id) { + this(id, null); + } + + public AbstractState(S id, Collection deferred) { + this(id, deferred, null, null); + } + + public AbstractState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + this.id = id; + this.deferred = deferred; + this.entryActions = entryActions; + this.exitActions = exitActions; + } + + @Override + public S getId() { + return id; + } + + @Override + public Collection getDeferredEvents() { + return deferred; + } + + @Override + public Collection getEntryActions() { + return entryActions; + } + + @Override + public Collection getExitActions() { + return exitActions; + } + + @Override + public String toString() { + return "AbstractState [id=" + id + ", deferred=" + deferred + ", entryActions=" + entryActions + + ", exitActions=" + exitActions + "]"; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java new file mode 100644 index 00000000..4c85cbde --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 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 + * + * http://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.state; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +public class EnumState, E extends Enum> extends AbstractState { + + public EnumState(S id) { + super(id); + } + + public EnumState(S id, Collection deferred) { + super(id, deferred); + } + + public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + super(id, deferred, entryActions, exitActions); + } + + @Override + public String toString() { + return "EnumState [getId()=" + getId() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + + ", toString()=" + super.toString() + "]"; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java new file mode 100644 index 00000000..4055a227 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015 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 + * + * http://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.state; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; + +/** + * {@code State} is an interface representing possible state in a state machine. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface State { + + /** + * Gets the state identifier. + * + * @return the identifier + */ + S getId(); + + /** + * Gets the deferred events for this state. + * + * @return the state deferred events + */ + Collection getDeferredEvents(); + + /** + * Gets {@link Action}s executed entering in this state. + * + * @return the state entry actions + */ + Collection getEntryActions(); + + /** + * Gets {@link Action}s executed exiting from this state. + * + * @return the state exit actions + */ + Collection getExitActions(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java new file mode 100644 index 00000000..8a06b3bf --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractExpressionEvaluator.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.convert.ConversionService; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * Base class providing common functionality for using Spring expression language. + * + * @author Mark Fisher + * @author Dave Syer + * @author Oleg Zhurakousky + * @author Artem Bilan + * @author Gary Russell + * @author Janne Valkealahti + * + */ +public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, InitializingBean { + + private volatile StandardEvaluationContext evaluationContext; + + private final ExpressionParser expressionParser = new SpelExpressionParser(); + + private final BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(); + + private volatile BeanFactory beanFactory; + + @Override + public void setBeanFactory(final BeanFactory beanFactory) { + if (beanFactory != null) { + this.beanFactory = beanFactory; + this.typeConverter.setBeanFactory(beanFactory); + if (this.evaluationContext != null && this.evaluationContext.getBeanResolver() == null) { + this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); + } + } + } + + @Override + public void afterPropertiesSet() throws Exception { + getEvaluationContext(); + } + + /** + * Sets the conversion service. + * + * @param conversionService the new conversion service + */ + public void setConversionService(ConversionService conversionService) { + if (conversionService != null) { + this.typeConverter.setConversionService(conversionService); + } + } + + /** + * Gets the evaluation context. + * + * @return the evaluation context + */ + protected StandardEvaluationContext getEvaluationContext() { + return this.getEvaluationContext(true); + } + + /** + * Emits a WARN log if the beanFactory field is null, unless the argument is false. + * @param beanFactoryRequired set to false to suppress the warning. + * @return The evaluation context. + */ + protected final StandardEvaluationContext getEvaluationContext(boolean beanFactoryRequired) { + if (this.evaluationContext == null) { + if (this.beanFactory == null && !beanFactoryRequired) { + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(); + } + else { + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); + } + if (this.typeConverter != null) { + this.evaluationContext.setTypeConverter(this.typeConverter); + } + } + return this.evaluationContext; + } + + protected Object evaluateExpression(String expression, Object input) { + return this.evaluateExpression(expression, input, (Class) null); + } + + protected T evaluateExpression(String expression, Object input, Class expectedType) { + return this.expressionParser.parseExpression(expression).getValue(this.getEvaluationContext(), input, expectedType); + } + + protected Object evaluateExpression(Expression expression, Object input) { + return this.evaluateExpression(expression, input, (Class) null); + } + + protected T evaluateExpression(Expression expression, Class expectedType) { + return expression.getValue(this.getEvaluationContext(), expectedType); + } + + protected Object evaluateExpression(Expression expression) { + return expression.getValue(this.getEvaluationContext()); + } + + protected T evaluateExpression(Expression expression, Object input, Class expectedType) { + return expression.getValue(this.getEvaluationContext(), input, expectedType); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java new file mode 100644 index 00000000..6d8efece --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -0,0 +1,326 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.OrderComparator; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.ExtendedState; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.statemachine.listener.CompositeStateMachineListener; +import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.processor.StateMachineHandler; +import org.springframework.statemachine.processor.StateMachineOnTransitionHandler; +import org.springframework.statemachine.processor.StateMachineRuntime; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; +import org.springframework.statemachine.transition.TransitionKind; +import org.springframework.statemachine.trigger.Trigger; +import org.springframework.util.Assert; + +/** + * Base implementation of a {@link StateMachine} loosely modelled from UML state + * machine. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public abstract class AbstractStateMachine extends LifecycleObjectSupport implements StateMachine, E> { + + private static final Log log = LogFactory.getLog(AbstractStateMachine.class); + + private final Collection> states; + + private final Collection> transitions; + + private final State initialState; + + private final ExtendedState extendedState; + + private final Queue> eventQueue = new ConcurrentLinkedQueue>(); + + private final LinkedList> deferList = new LinkedList>(); + + private final CompositeStateMachineListener stateListener = new CompositeStateMachineListener(); + + private volatile State currentState; + + private volatile Runnable task; + + /** + * Instantiates a new abstract state machine. + * + * @param states the states of this machine + * @param transitions the transitions of this machine + * @param initialState the initial state of this machine + */ + public AbstractStateMachine(Collection> states, Collection> transitions, + State initialState) { + this(states, transitions, initialState, new DefaultExtendedState()); + } + + /** + * Instantiates a new abstract state machine. + * + * @param states the states of this machine + * @param transitions the transitions of this machine + * @param initialState the initial state of this machine + * @param extendedState the extended state of this machine + */ + public AbstractStateMachine(Collection> states, Collection> transitions, + State initialState, ExtendedState extendedState) { + super(); + this.states = states; + this.transitions = transitions; + this.initialState = initialState; + this.extendedState = extendedState; + } + + @Override + public State getState() { + return currentState; + } + + @Override + public State getInitialState() { + return initialState; + } + + @Override + public void sendEvent(Message event) { + // TODO: machine header looks weird! + event = MessageBuilder.fromMessage(event).setHeader("machine", this).build(); + if (log.isDebugEnabled()) { + log.debug("Queue event " + event); + } + eventQueue.add(event); + scheduleEventQueueProcessing(); + } + + @Override + public void sendEvent(E event) { + sendEvent(MessageBuilder.withPayload(event).build()); + } + + @Override + protected void doStart() { + super.doStart(); + switchToState(initialState, null); + } + + @Override + public void addStateListener(StateMachineListener, E> listener) { + stateListener.register(listener); + } + + /** + * Gets the {@link State}s defined in this machine. Returned collection is + * an unmodifiable copy because states in a state machine are immutable. + * + * @return immutable copy of existing states + */ + public Collection> getStates() { + return Collections.unmodifiableCollection(states); + } + + private void switchToState(State state, Message event) { + log.info("Moving into state=" + state + " from " + currentState); + + exitFromState(currentState, event); + stateListener.stateChanged(currentState, state); + + callHandlers(currentState, state, event); + + currentState = state; + entryToState(state, event); + + for (Transition transition : transitions) { + State source = transition.getSource(); + State target = transition.getTarget(); + if (transition.getTrigger() == null && source.equals(currentState)) { + switchToState(target, event); + } + + } + + } + + private void exitFromState(State state, Message event) { + if (state != null) { + MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( + new HashMap()); + Collection actions = state.getExitActions(); + if (actions != null) { + for (Action action : actions) { + action.execute(new DefaultStateContext(messageHeaders, extendedState)); + } + } + } + } + + private void entryToState(State state, Message event) { + if (state != null) { + MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( + new HashMap()); + Collection actions = state.getEntryActions(); + if (actions != null) { + for (Action action : actions) { + action.execute(new DefaultStateContext(messageHeaders, extendedState)); + } + } + } + } + + private void processEventQueue() { + log.debug("Process event queue"); + Message queuedEvent = null; + while ((queuedEvent = eventQueue.poll()) != null) { + Message defer = null; + for (Transition transition : transitions) { + State source = transition.getSource(); + State target = transition.getTarget(); + Trigger trigger = transition.getTrigger(); + if (source.equals(currentState)) { + if (trigger != null && trigger.evaluate(queuedEvent.getPayload())) { + boolean transit = transition.transit(new DefaultStateContext(queuedEvent.getHeaders(), extendedState)); + if (transit && transition.getKind() != TransitionKind.INTERNAL) { + switchToState(target, queuedEvent); + } + break; + } else if (source.getDeferredEvents() != null && source.getDeferredEvents().contains(queuedEvent.getPayload())) { + defer = queuedEvent; + } + } + } + if (defer != null) { + log.info("Deferring event " + defer); + deferList.addLast(defer); + } + } + } + + private void processDeferList() { + log.debug("Process defer list"); + ListIterator> iterator = deferList.listIterator(); + while (iterator.hasNext()) { + Message event = iterator.next(); + for (Transition transition : transitions) { + State source = transition.getSource(); + State target = transition.getTarget(); + Trigger trigger = transition.getTrigger(); + if (source.equals(currentState)) { + if (trigger != null && trigger.evaluate(event.getPayload())) { + boolean transit = transition.transit(new DefaultStateContext(event.getHeaders(), extendedState)); + if (transit && transition.getKind() != TransitionKind.INTERNAL) { + switchToState(target, event); + } + iterator.remove(); + } + } + } + } + } + + private void scheduleEventQueueProcessing() { + if (task == null) { + task = new Runnable() { + @Override + public void run() { + processEventQueue(); + processDeferList(); + task = null; + } + }; + getTaskExecutor().execute(task); + } + } + + private void callHandlers(State sourceState, State targetState, Message event) { + if (sourceState != null && targetState != null) { + MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( + new HashMap()); + StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState); + getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext); + } + + } + + + private List getStateMachineHandlerResults(List stateMachineHandlers, final StateContext stateContext) { + StateMachineRuntime runtime = new StateMachineRuntime() { + @Override + public StateContext getStateContext() { + return stateContext; + } + }; + List results = new ArrayList(); + for (StateMachineHandler handler : stateMachineHandlers) { + results.add(handler.handle(runtime)); + } + return results; + } + + private List getStateMachineHandlers(State sourceState, State targetState) { + BeanFactory beanFactory = getBeanFactory(); + + // TODO think how to handle null bf + if (beanFactory == null) { + return Collections.emptyList(); + } + Assert.state(beanFactory instanceof ListableBeanFactory, "Bean factory must be instance of ListableBeanFactory"); + Map handlers = ((ListableBeanFactory) beanFactory) + .getBeansOfType(StateMachineOnTransitionHandler.class); + List handlersList = new ArrayList(); + + for (Entry entry : handlers.entrySet()) { + OnTransition annotation = entry.getValue().getAnnotation(); + String source = annotation.source(); + String target = annotation.target(); + String s = sourceState.getId().toString(); + String t = targetState.getId().toString(); + if (s.equals(source) && t.equals(target)) { + handlersList.add(entry.getValue()); + } + } + + OrderComparator comparator = new OrderComparator(); + Collections.sort(handlersList, comparator); + return handlersList; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java new file mode 100644 index 00000000..b893ebc8 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AnnotatedMethodFilter.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.MethodFilter; +import org.springframework.util.StringUtils; + +/** + * A MethodFilter implementation that enables the following: + *
    + *
  1. matching on method name, if available
  2. + *
  3. exclusion of void-returning methods if 'requiresReply' is true
  4. + *
  5. limiting to annotated methods if at least one is present
  6. + *
+ *

+ * + * @author Mark Fisher + * @author Janne Valkealahti + */ +public class AnnotatedMethodFilter implements MethodFilter { + + private final Class annotationType; + + private final String methodName; + + private final boolean requiresReply; + + public AnnotatedMethodFilter(Class annotationType, String methodName, boolean requiresReply) { + this.annotationType = annotationType; + this.methodName = methodName; + this.requiresReply = requiresReply; + } + + public List filter(List methods) { + List annotatedCandidates = new ArrayList(); + List fallbackCandidates = new ArrayList(); + for (Method method : methods) { + if (method.isBridge()) { + continue; + } + if (this.requiresReply && method.getReturnType().equals(void.class)) { + continue; + } + if (StringUtils.hasText(this.methodName) && !this.methodName.equals(method.getName())) { + continue; + } + if (this.annotationType != null && AnnotationUtils.findAnnotation(method, this.annotationType) != null) { + annotatedCandidates.add(method); + } else { + fallbackCandidates.add(method); + } + } + return (!annotatedCandidates.isEmpty()) ? annotatedCandidates : fallbackCandidates; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java new file mode 100644 index 00000000..121c3e88 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/BeanFactoryTypeConverter.java @@ -0,0 +1,154 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.beans.PropertyEditor; + +import org.springframework.beans.BeansException; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.expression.TypeConverter; +import org.springframework.util.ClassUtils; + +/** + * @author Dave Syer + * @author Oleg Zhurakousky + * @author Gary Russell + * @author Soby Chacko + * @author Janne Valkealahti + * + */ +public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware { + + private static ConversionService defaultConversionService; + + private volatile SimpleTypeConverter delegate = new SimpleTypeConverter(); + + private volatile boolean haveCalledDelegateGetDefaultEditor; + + private volatile ConversionService conversionService; + + /** + * Instantiates a new bean factory type converter. + */ + public BeanFactoryTypeConverter() { + synchronized (BeanFactoryTypeConverter.class) { + if (defaultConversionService == null) { + defaultConversionService = new DefaultConversionService(); + } + } + this.conversionService = defaultConversionService; + } + + public BeanFactoryTypeConverter(ConversionService conversionService) { + this.conversionService = conversionService; + } + + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof ConfigurableBeanFactory) { + Object typeConverter = ((ConfigurableBeanFactory) beanFactory).getTypeConverter(); + if (typeConverter instanceof SimpleTypeConverter) { + delegate = (SimpleTypeConverter) typeConverter; + } + } + } + + public boolean canConvert(Class sourceType, Class targetType) { + if (conversionService.canConvert(sourceType, targetType)) { + return true; + } + if (!String.class.isAssignableFrom(sourceType) && !String.class.isAssignableFrom(targetType)) { + // PropertyEditor cannot convert non-Strings + return false; + } + if (!String.class.isAssignableFrom(sourceType)) { + return delegate.findCustomEditor(sourceType, null) != null || this.getDefaultEditor(sourceType) != null; + } + return delegate.findCustomEditor(targetType, null) != null || this.getDefaultEditor(targetType) != null; + } + + public boolean canConvert(TypeDescriptor sourceTypeDescriptor, TypeDescriptor targetTypeDescriptor) { + if (conversionService.canConvert(sourceTypeDescriptor, targetTypeDescriptor)) { + return true; + } + // TODO: what does this mean? This method is not used in SpEL so + // probably ignorable? + Class sourceType = sourceTypeDescriptor.getObjectType(); + Class targetType = targetTypeDescriptor.getObjectType(); + return canConvert(sourceType, targetType); + } + + public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) { + // Echoes + // org.springframework.expression.common.ExpressionUtils.convertTypedValue() + if ((targetType.getType() == Void.class || targetType.getType() == Void.TYPE) && value == null) { + return null; + } + if (sourceType != null) { + Class sourceClass = sourceType.getType(); + //Class targetClass = targetType.getType(); + if (sourceType.isAssignableTo(targetType) && ClassUtils.isPrimitiveArray(sourceClass)) { + return value; + } + } + if (conversionService.canConvert(sourceType, targetType)) { + return conversionService.convert(value, sourceType, targetType); + } + if (!String.class.isAssignableFrom(sourceType.getType())) { + PropertyEditor editor = delegate.findCustomEditor(sourceType.getType(), null); + if (editor == null) { + editor = this.getDefaultEditor(sourceType.getType()); + } + if (editor != null) { // INT-1441 + String text = null; + synchronized (editor) { + editor.setValue(value); + text = editor.getAsText(); + } + if (String.class.isAssignableFrom(targetType.getType())) { + return text; + } + return convertValue(text, TypeDescriptor.valueOf(String.class), targetType); + } + } + return delegate.convertIfNecessary(value, targetType.getType()); + } + + private PropertyEditor getDefaultEditor(Class sourceType) { + PropertyEditor defaultEditor; + if (this.haveCalledDelegateGetDefaultEditor) { + defaultEditor = delegate.getDefaultEditor(sourceType); + } else { + synchronized (this) { + // not thread-safe - it builds the defaultEditors field in-place + // (SPR-10191) + defaultEditor = delegate.getDefaultEditor(sourceType); + } + this.haveCalledDelegateGetDefaultEditor = true; + } + return defaultEditor; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java new file mode 100644 index 00000000..fd59d0ba --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultExtendedState.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.statemachine.ExtendedState; + +/** + * Default implementation of a {@link ExtendedState}. + * + * @author Janne Valkealahti + * + */ +public class DefaultExtendedState implements ExtendedState { + + private final Map variables; + + /** + * Instantiates a new default extended state. + */ + public DefaultExtendedState() { + this.variables = new HashMap(); + } + + @Override + public Map getVariables() { + return variables; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java new file mode 100644 index 00000000..db0e7949 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.statemachine.ExtendedState; +import org.springframework.statemachine.StateContext; + +public class DefaultStateContext implements StateContext { + + private final MessageHeaders messageHeaders; + + private final ExtendedState extendedState; + + public DefaultStateContext(MessageHeaders messageHeaders, ExtendedState extendedState) { + this.messageHeaders = messageHeaders; + this.extendedState = extendedState; + } + + @Override + public MessageHeaders getMessageHeaders() { + return messageHeaders; + } + + @Override + public ExtendedState getExtendedState() { + return extendedState; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java new file mode 100644 index 00000000..8a5bb36e --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ExpressionUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.context.expression.MapAccessor; +import org.springframework.core.convert.ConversionService; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeConverter; + +/** + * Utility class with static methods for helping with establishing environments for + * SpEL expressions. + * + * @author Gary Russell + * @author Oleg Zhurakousky + * @author Artem Bilan + */ +public abstract class ExpressionUtils { + + private static final Log logger = LogFactory.getLog(ExpressionUtils.class); + + /** + * Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its + * property accessor property and the supplied {@link ConversionService} in its + * conversionService property. + * + * @param conversionService the conversion service. + * @return the evaluation context. + */ + private static StandardEvaluationContext createStandardEvaluationContext(ConversionService conversionService, + BeanFactory beanFactory) { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + evaluationContext.addPropertyAccessor(new MapAccessor()); + if (conversionService != null) { + evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService)); + } + if (beanFactory != null) { + evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); + } + return evaluationContext; + } + + /** + * Used to create a context with no BeanFactory, usually in tests. + * @return The evaluation context. + */ + public static StandardEvaluationContext createStandardEvaluationContext() { + return doCreateContext(null); + } + + /** + * Obtains the context from the beanFactory if not null; emits a warning if the beanFactory + * is null. + * @param beanFactory The bean factory. + * @return The evaluation context. + */ + public static StandardEvaluationContext createStandardEvaluationContext(BeanFactory beanFactory) { + if (beanFactory == null) { + logger.warn("Creating EvaluationContext with no beanFactory", new RuntimeException("No beanfactory")); + } + return doCreateContext(beanFactory); + } + + private static StandardEvaluationContext doCreateContext(BeanFactory beanFactory) { + ConversionService conversionService = null; + StandardEvaluationContext evaluationContext = null; + if (beanFactory != null) { + evaluationContext = StateMachineContextUtils.getEvaluationContext(beanFactory); + } + if (evaluationContext == null) { + if (beanFactory != null) { + conversionService = StateMachineContextUtils.getConversionService(beanFactory); + } + evaluationContext = createStandardEvaluationContext(conversionService, beanFactory); + } + return evaluationContext; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java new file mode 100644 index 00000000..83c52bdb --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/FixedMethodFilter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.expression.MethodFilter; +import org.springframework.util.Assert; + +/** + * A {@link MethodFilter} implementation that will always return the same Method + * instance within a single-element list if it is present in the candidate list. + * If the Method is not present in the candidate list, it will return an empty + * list. + * + * @author Mark Fisher + * @author Gary Russell + * @author Janne Valkealahti + */ +public class FixedMethodFilter implements MethodFilter { + + private final Method method; + + public FixedMethodFilter(Method method) { + Assert.notNull(method, "method must not be null"); + this.method = method; + } + + public List filter(List methods) { + if (methods != null && methods.contains(this.method)) { + List filteredList = new ArrayList(1); + filteredList.add(this.method); + return filteredList; + } + return Collections. emptyList(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java new file mode 100644 index 00000000..7776aebf --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/LifecycleObjectSupport.java @@ -0,0 +1,250 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; + +/** + * Convenient base class for object which needs spring task scheduler, task + * executor and life cycle handling. + * + * @author Janne Valkealahti + * + */ +public abstract class LifecycleObjectSupport implements InitializingBean, SmartLifecycle, BeanFactoryAware { + + private static final Log log = LogFactory.getLog(LifecycleObjectSupport.class); + + // fields for lifecycle + private volatile boolean autoStartup = true; + private volatile int phase = 0; + private volatile boolean running; + + // lock to protect lifycycle methods + private final ReentrantLock lifecycleLock = new ReentrantLock(); + + // common task handling + private TaskScheduler taskScheduler; + private TaskExecutor taskExecutor; + + // to access bean factory + private volatile BeanFactory beanFactory; + + @Override + public final void afterPropertiesSet() { + try { + this.onInit(); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + throw new BeanInitializationException("failed to initialize", e); + } + } + + @Override + public final void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.notNull(beanFactory, "beanFactory must not be null"); + if(log.isDebugEnabled()) { + log.debug("Setting bean factory: " + beanFactory + " for " + this); + } + this.beanFactory = beanFactory; + } + + @Override + public final boolean isAutoStartup() { + return this.autoStartup; + } + + @Override + public final int getPhase() { + return this.phase; + } + + @Override + public final boolean isRunning() { + this.lifecycleLock.lock(); + try { + return this.running; + } finally { + this.lifecycleLock.unlock(); + } + } + + @Override + public final void start() { + this.lifecycleLock.lock(); + try { + if (!this.running) { + this.doStart(); + this.running = true; + if (log.isInfoEnabled()) { + log.info("started " + this); + } else { + if(log.isDebugEnabled()) { + log.debug("already started " + this); + } + } + } + } finally { + this.lifecycleLock.unlock(); + } + } + + @Override + public final void stop() { + this.lifecycleLock.lock(); + try { + if (this.running) { + this.doStop(); + this.running = false; + if (log.isInfoEnabled()) { + log.info("stopped " + this); + } + } else { + if (log.isDebugEnabled()) { + log.debug("already stopped " + this); + } + } + } finally { + this.lifecycleLock.unlock(); + } + } + + @Override + public final void stop(Runnable callback) { + this.lifecycleLock.lock(); + try { + this.stop(); + callback.run(); + } finally { + this.lifecycleLock.unlock(); + } + } + + /** + * Sets the auto startup. + * + * @param autoStartup the new auto startup + * @see SmartLifecycle + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * Sets the phase. + * + * @param phase the new phase + * @see SmartLifecycle + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * Gets the {@link BeanFactory} for this instance. + * + * @return the bean factory. + */ + protected final BeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * Sets the used {@link TaskScheduler}. + * + * @param taskScheduler the task scheduler + */ + public void setTaskScheduler(TaskScheduler taskScheduler) { + Assert.notNull(taskScheduler, "taskScheduler must not be null"); + this.taskScheduler = taskScheduler; + } + + /** + * Gets the defined {@link TaskScheduler}. + * + * @return the defined task scheduler + */ + protected TaskScheduler getTaskScheduler() { + if(taskScheduler == null && getBeanFactory() != null) { + if(log.isDebugEnabled()) { + log.debug("getting taskScheduler service from bean factory " + getBeanFactory()); + } + taskScheduler = StateMachineContextUtils.getTaskScheduler(getBeanFactory()); + } + return taskScheduler; + } + + /** + * Sets the used {@link TaskExecutor}. + * + * @param taskExecutor the task executor + */ + public void setTaskExecutor(TaskExecutor taskExecutor) { + Assert.notNull(taskExecutor, "taskExecutor must not be null"); + this.taskExecutor = taskExecutor; + } + + /** + * Gets the defined {@link TaskExecutor}. + * + * @return the defined task executor + */ + protected TaskExecutor getTaskExecutor() { + if(taskExecutor == null && getBeanFactory() != null) { + if(log.isDebugEnabled()) { + log.debug("getting taskExecutor service from bean factory " + getBeanFactory()); + } + taskExecutor = StateMachineContextUtils.getTaskExecutor(getBeanFactory()); + } + return taskExecutor; + } + + /** + * Subclasses may implement this for initialization logic. Called + * during the {@link InitializingBean} phase. Implementor should + * always call super method not to break initialization chain. + * + * @throws Exception exception + */ + protected void onInit() throws Exception {} + + /** + * Subclasses may implement this method with the start behavior. This + * method will be invoked while holding the {@link #lifecycleLock}. + */ + protected void doStart() {}; + + /** + * Subclasses may implement this method with the stop behavior. This method + * will be invoked while holding the {@link #lifecycleLock}. + */ + protected void doStop() {}; + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java new file mode 100644 index 00000000..86314df2 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineContextUtils.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.task.TaskExecutor; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; + +/** + * Utility methods for accessing common components from the BeanFactory. + * + * @author Janne Valkealahti + * + */ +public class StateMachineContextUtils { + + /* Default task scheduler bean name */ + public static final String TASK_SCHEDULER_BEAN_NAME = "taskScheduler"; + + /* Default task executor bean name */ + public static final String TASK_EXECUTOR_BEAN_NAME = "taskExecutor"; + + /* Default conversion service bean name */ + public static final String CONVERSION_SERVICE_BEAN_NAME = "cloudClusterConversionService"; + + /* Default evaluation context bean name */ + public static final String EVALUATION_CONTEXT_BEAN_NAME = "cloudClusterEvaluationContext"; + + /** + * Return the {@link TaskScheduler} bean whose name is "taskScheduler" if + * available. + * + * @param beanFactory BeanFactory for lookup, must not be null. + * @return task scheduler + */ + public static TaskScheduler getTaskScheduler(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class); + } + + /** + * Return the {@link TaskScheduler} bean whose name is "taskExecutor" if + * available. + * + * @param beanFactory BeanFactory for lookup, must not be null. + * @return task executor + */ + public static TaskExecutor getTaskExecutor(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, TASK_EXECUTOR_BEAN_NAME, TaskExecutor.class); + } + + /** + * Return the {@link ConversionService} bean whose name is + * "yarnConversionService" if available. + * + * @param beanFactory + * BeanFactory for lookup, must not be null. + * + * @return The {@link ConversionService} bean whose name is + * "yarnConversionService" if available. + */ + public static ConversionService getConversionService(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); + } + + /** + * Return the {@link StandardEvaluationContext} bean whose name is + * "yarnEvaluationContext" if available. + * + * @param beanFactory BeanFactory for lookup, must not be null. + * + * @return the instance of {@link StandardEvaluationContext} bean whose name + * is "yarnEvaluationContext" . + */ + public static StandardEvaluationContext getEvaluationContext(BeanFactory beanFactory) { + return getBeanOfType(beanFactory, EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class); + } + + /** + * Gets a bean from a factory with a given name and type. + * + * @param beanFactory the bean factory + * @param beanName the bean name + * @param type the type as of a class + * @return Bean known to a bean factory, null if not found. + */ + private static T getBeanOfType(BeanFactory beanFactory, String beanName, Class type) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + if (!beanFactory.containsBean(beanName)) { + return null; + } + return beanFactory.getBean(beanName, type); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java new file mode 100644 index 00000000..a534e142 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/UniqueMethodFilter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 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 + * + * http://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.support; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.util.ReflectionUtils.MethodFilter; + +/** + * A {@link MethodFilter} implementation that will match unique methods. + * + * @author Oleg Zhurakousky + * @author Janne Valkealahti + */ +public class UniqueMethodFilter implements MethodFilter { + + private final List uniqueMethods = new ArrayList(); + + public UniqueMethodFilter(Class targetClass) { + ArrayList allMethods = new ArrayList(Arrays.asList(targetClass.getMethods())); + for (Method method : allMethods) { + this.uniqueMethods.add(org.springframework.util.ClassUtils.getMostSpecificMethod(method, targetClass)); + } + } + + public boolean matches(Method method) { + return this.uniqueMethods.contains(method); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java new file mode 100644 index 00000000..23189c03 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public abstract class AbstractExternalTransition extends AbstractTransition implements Transition { + + public AbstractExternalTransition(State source, State target, Collection actions, E event, Guard guard) { + super(source, target, actions, event, TransitionKind.EXTERNAL, guard); + } + + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java new file mode 100644 index 00000000..d278e67f --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class AbstractInternalTransition extends AbstractTransition implements Transition { + + public AbstractInternalTransition(State source,Collection actions, E event, Guard guard) { + super(source, source, actions, event, TransitionKind.INTERNAL, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java new file mode 100644 index 00000000..de101a39 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class AbstractLocalTransition extends AbstractTransition implements Transition { + + public AbstractLocalTransition(State source, State target,Collection actions, E event, Guard guard) { + super(source, target, actions, event, TransitionKind.LOCAL, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java new file mode 100644 index 00000000..e3845ca3 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.trigger.EventTrigger; +import org.springframework.statemachine.trigger.Trigger; +import org.springframework.util.Assert; + +/** + * Base implementation of a {@link Transition}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public abstract class AbstractTransition implements Transition { + + private final State source; + + private final State target; + + private final Collection actions; + + private final TransitionKind kind; + + private final Guard guard; + + private Trigger trigger; + + public AbstractTransition(State source, State target, Collection actions, E event, + TransitionKind kind, Guard guard) { + Assert.notNull(source, "Source must be set"); +// Assert.notNull(target, "Target must be set"); + Assert.notNull(kind, "Transition type must be set"); + this.source = source; + this.target = target; + this.actions = actions; + if (event != null) { + this.trigger = new EventTrigger(event); + } + this.kind = kind; + this.guard = guard; + } + + @Override + public State getSource() { + return source; + } + + @Override + public State getTarget() { + return target; + } + + @Override + public Collection getActions() { + return actions; + } + + @Override + public Trigger getTrigger() { + return trigger; + } + + @Override + public boolean transit(StateContext context) { + if (guard != null) { + if (!guard.evaluate(context)) { + return false; + } + } + if (actions != null) { + for (Action action : actions) { + action.execute(context); + } + } + return true; + } + + @Override + public TransitionKind getKind() { + return kind; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java new file mode 100644 index 00000000..a167a2e0 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class DefaultExternalTransition extends AbstractExternalTransition { + + public DefaultExternalTransition(State source, State target, Collection actions, E event, Guard guard) { + super(source, target, actions, event, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java new file mode 100644 index 00000000..08d428de --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class DefaultInternalTransition extends AbstractInternalTransition { + + public DefaultInternalTransition(State source, Collection actions, E event, Guard guard) { + super(source, actions, event, guard); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java new file mode 100644 index 00000000..7cb9c7dd --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import java.util.Collection; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.trigger.Trigger; + +/** + * {@code Transition} is something what a state machine associates with a state + * changes. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface Transition { + + boolean transit(StateContext context); + + State getSource(); + + State getTarget(); + + Collection getActions(); + + Trigger getTrigger(); + + TransitionKind getKind(); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java new file mode 100644 index 00000000..f557a992 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/TransitionKind.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +/** + * Defines enumeration of a {@link Transition} kind. This is uses within a + * transition to indicate whether its type is external, internal or local. + * + * @author Janne Valkealahti + * + */ +public enum TransitionKind { + + /** Indicates an external transition kind. */ + EXTERNAL, + + /** Indicates an internal transition kind. */ + INTERNAL, + + /** Indicates a local transition kind. */ + LOCAL + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java new file mode 100644 index 00000000..d0548226 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 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 + * + * http://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.trigger; + +public class EventTrigger implements Trigger { + + private final E event; + + public EventTrigger(E event) { + this.event = event; + } + + @Override + public boolean evaluate(E event) { + return this.event.equals(event); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java new file mode 100644 index 00000000..17f05175 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 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 + * + * http://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.trigger; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code Trigger} is the cause of the {@link Transition}. Cause is usually an + * event but can be some other signal or a change in some condition. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface Trigger { + + boolean evaluate(E event); + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java new file mode 100644 index 00000000..4d95cb52 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import java.util.concurrent.CountDownLatch; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; + +/** + * Base class for stace machine tests. + * + * @author Janne Valkealahti + * + */ +public abstract class AbstractStateMachineTests { + + public enum TestStates { + SI,S1,S2,S3,S4 + } + + public enum TestEvents { + E1,E2,E3,E4 + } + + @Configuration + public static class BaseConfig { + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + + public static class TestEntryAction extends AbstractTestAction { + } + + public static class TestExitAction extends AbstractTestAction { + } + + public static class TestAction extends AbstractTestAction { + } + + public static class TestGuard implements Guard { + + public CountDownLatch onEvaluateLatch = new CountDownLatch(1); + + boolean evaluationResult = true; + + public TestGuard() { + } + + public TestGuard(boolean evaluationResult) { + this.evaluationResult = evaluationResult; + } + + @Override + public boolean evaluate(StateContext context) { + onEvaluateLatch.countDown(); + return evaluationResult; + } + } + + protected static class AbstractTestAction implements Action { + + public CountDownLatch onExecuteLatch = new CountDownLatch(1); + + @Override + public void execute(StateContext context) { + onExecuteLatch.countDown(); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java new file mode 100644 index 00000000..ea352a91 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java @@ -0,0 +1,211 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.DefaultInternalTransition; +import org.springframework.statemachine.transition.Transition; + +public class EnumStateMachineTests extends AbstractStateMachineTests { + + @Test + public void testSimpleStateSwitch() { + + State stateSI = new EnumState(TestStates.SI); + State stateS1 = new EnumState(TestStates.S1); + State stateS2 = new EnumState(TestStates.S2); + State stateS3 = new EnumState(TestStates.S3); + + Collection> states = new ArrayList>(); + states.add(stateSI); + states.add(stateS1); + states.add(stateS2); + states.add(stateS3); + + Collection> transitions = new ArrayList>(); + + Collection actionsFromSIToS1 = new ArrayList(); + actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1")); + DefaultExternalTransition transitionFromSIToS1 = + new DefaultExternalTransition(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null); + + Collection actionsFromS1ToS2 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2")); + DefaultExternalTransition transitionFromS1ToS2 = + new DefaultExternalTransition(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null); + + Collection actionsFromS2ToS3 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3")); + DefaultExternalTransition transitionFromS2ToS3 = + new DefaultExternalTransition(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null); + + transitions.add(transitionFromSIToS1); + transitions.add(transitionFromS1ToS2); + transitions.add(transitionFromS2ToS3); + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + State initialState = machine.getInitialState(); + assertThat(initialState, is(stateSI)); + + State state = machine.getState(); + assertThat(state, is(stateSI)); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + state = machine.getState(); + assertThat(state, is(stateS1)); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + state = machine.getState(); + assertThat(state, is(stateS2)); + + // not processed + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + state = machine.getState(); + assertThat(state, is(stateS2)); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build()); + state = machine.getState(); + assertThat(state, is(stateS3)); + } + + @Test + public void testDeferredEvents() { + + Collection deferred = new ArrayList(); + deferred.add(TestEvents.E2); + deferred.add(TestEvents.E3); + + // states + State stateSI = new EnumState(TestStates.SI, deferred); + State stateS1 = new EnumState(TestStates.S1); + State stateS2 = new EnumState(TestStates.S2); + State stateS3 = new EnumState(TestStates.S3); + + Collection> states = new ArrayList>(); + states.add(stateSI); + states.add(stateS1); + states.add(stateS2); + states.add(stateS3); + + // transitions + Collection> transitions = new ArrayList>(); + + Collection actionsFromSIToS1 = new ArrayList(); + actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1")); + DefaultExternalTransition transitionFromSIToS1 = + new DefaultExternalTransition(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null); + + Collection actionsFromS1ToS2 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2")); + DefaultExternalTransition transitionFromS1ToS2 = + new DefaultExternalTransition(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null); + + Collection actionsFromS2ToS3 = new ArrayList(); + actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3")); + DefaultExternalTransition transitionFromS2ToS3 = + new DefaultExternalTransition(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null); + + transitions.add(transitionFromSIToS1); + transitions.add(transitionFromS1ToS2); + transitions.add(transitionFromS2ToS3); + + // create machine + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); +// StateMachine, TestEvents> machine2 = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + State initialState = machine.getInitialState(); + assertThat(initialState, is(stateSI)); + + State state = machine.getState(); + assertThat(state, is(stateSI)); + + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build()); + state = machine.getState(); + assertThat(state, is(stateSI)); + + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + state = machine.getState(); + assertThat(state, is(stateS3)); + } + + @Test + public void testInternalTransitions() { + State stateSI = new EnumState(TestStates.SI); + + Collection> states = new ArrayList>(); + states.add(stateSI); + + Collection actionsInSI = new ArrayList(); + actionsInSI.add(new LoggingAction("actionsInSI")); + DefaultInternalTransition transitionInternalSI = + new DefaultInternalTransition(stateSI, actionsInSI, TestEvents.E1, null); + + // transitions + Collection> transitions = new ArrayList>(); + transitions.add(transitionInternalSI); + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + } + + private static class LoggingAction implements Action { + + private static final Log log = LogFactory.getLog(LoggingAction.class); + + private String message; + + public LoggingAction(String message) { + this.message = message; + } + + @Override + public void execute(StateContext context) { + log.info("Hello from LoggingAction " + message); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java new file mode 100644 index 00000000..57fafff6 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.EnumStateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.state.State; + +public class StateMachineFactoryTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testMachineFromFactory() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + + EnumStateMachineFactory stateMachineFactory = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, EnumStateMachineFactory.class); + StateMachine, TestEvents> machine = stateMachineFactory.getStateMachine(); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getId(), is(TestStates.S2)); + ctx.close(); + } + + @Configuration + @EnableStateMachineFactory + static class Config extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java new file mode 100644 index 00000000..775ebace --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +public class StateMachineTests extends AbstractStateMachineTests { + + @Test + public void test1() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + @SuppressWarnings("unchecked") + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build()); + ctx.close(); + } + + private static class LoggingAction implements Action { + + private static final Log log = LogFactory.getLog(StateMachineTests.LoggingAction.class); + + private String message; + + public LoggingAction(String message) { + this.message = message; + } + + @Override + public void execute(StateContext context) { + log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo")); + } + + } + + @Configuration + @EnableStateMachine + static class Config extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3, TestEvents.E4) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(loggingAction()) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S4) + .target(TestStates.S3) + .event(TestEvents.E4) + .action(loggingAction()); + } + + @Bean + public LoggingAction loggingAction() { + return new LoggingAction("as bean"); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java new file mode 100644 index 00000000..06f6c3c3 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/TestUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 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 + * + * http://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; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.springframework.util.ReflectionUtils; + +/** + * Utils for tests. + * + * @author Janne Valkealahti + * + */ +public class TestUtils { + + @SuppressWarnings("unchecked") + public static T readField(String name, Object target) throws Exception { + Field field = null; + Class clazz = target.getClass(); + do { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ex) { + } + + clazz = clazz.getSuperclass(); + } while (field == null && !clazz.equals(Object.class)); + + if (field == null) + throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " + + target.getClass()); + field.setAccessible(true); + return (T) field.get(target); + } + + @SuppressWarnings("unchecked") + public static T callMethod(String name, Object target) throws Exception { + Class clazz = target.getClass(); + Method method = ReflectionUtils.findMethod(clazz, name); + + if (method == null) + throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " + + target.getClass()); + method.setAccessible(true); + return (T) ReflectionUtils.invokeMethod(method, target); + } + + public static void setField(String name, Object target, Object value) throws Exception { + Field field = null; + Class clazz = target.getClass(); + do { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ex) { + } + + clazz = clazz.getSuperclass(); + } while (field == null && !clazz.equals(Object.class)); + + if (field == null) + throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " + + target.getClass()); + field.setAccessible(true); + field.set(target, value); + } + + @SuppressWarnings("unchecked") + public static T callMethod(String name, Object target, Object[] args, Class[] argsTypes) throws Exception { + Class clazz = target.getClass(); + Method method = ReflectionUtils.findMethod(clazz, name, argsTypes); + + if (method == null) + throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " + + target.getClass()); + method.setAccessible(true); + return (T) ReflectionUtils.invokeMethod(method, target, args); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java new file mode 100644 index 00000000..d0f1119c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015 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 + * + * http://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.action; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * Tests for state machine actions. + * + * @author Janne Valkealahti + * + */ +public class ActionTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testTransitionActions() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + StateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + + TestCountAction testAction1 = ctx.getBean("testAction1", TestCountAction.class); + TestCountAction testAction2 = ctx.getBean("testAction2", TestCountAction.class); + TestCountAction testAction3 = ctx.getBean("testAction3", TestCountAction.class); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build()); + assertThat(testAction1.count, is(1)); + assertThat(testAction2.count, is(1)); + assertThat(testAction3.count, is(1)); + ctx.close(); + } + + @Test + public void testEventFromAction() { + + } + + private static class TestCountAction implements Action { + + int count = 0; + + public TestCountAction() { + count = 0; + } + + @Override + public void execute(StateContext context) { + count++; + } + + } + + @Configuration + @EnableStateMachine + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(testAction1()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2) + .action(testAction2()) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3) + .action(testAction3()); + } + + @Bean + public TestCountAction testAction1() { + return new TestCountAction(); + } + + @Bean + public TestCountAction testAction2() { + return new TestCountAction(); + } + + @Bean + public TestCountAction testAction3() { + return new TestCountAction(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java new file mode 100644 index 00000000..18bd8bcf --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/annotation/MethodAnnotationTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015 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 + * + * http://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.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.processor.StateMachineAnnotationPostProcessor; + +public class MethodAnnotationTests extends AbstractStateMachineTests { + + @Test + @SuppressWarnings("unchecked") + public void testMethodAnnotations() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BaseConfig.class, AnnoConfig.class, BeanConfig1.class, Config1.class); + + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(context.containsBean("fooMachine"), is(true)); + + Bean1 bean1 = context.getBean(Bean1.class); + + // this event should cause 'method1' to get called + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + + assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(bean1.onOnTransitionFromS2ToS3Latch.await(2, TimeUnit.SECONDS), is(false)); + + context.close(); + } + + @WithStateMachine(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE) + static class Bean1 { + + CountDownLatch onMethod1Latch = new CountDownLatch(1); + CountDownLatch onOnTransitionFromS2ToS3Latch = new CountDownLatch(1); + + @OnTransition(source = "S1", target = "S2") + public void method1() { + onMethod1Latch.countDown(); + } + + @OnTransition + public void onTransitionFromS2ToS3() { + onOnTransitionFromS2ToS3Latch.countDown(); + } + + + } + + @Configuration + static class BeanConfig1 { + + @Bean + public Bean1 bean1() { + return new Bean1(); + } + + } + + + @Configuration + static class AnnoConfig { + + @Bean(name="org.springframework.statemachine.internal.springStateMachineAnnotationPostProcessor") + public StateMachineAnnotationPostProcessor springStateMachineAnnotationPostProcessor() { + return new StateMachineAnnotationPostProcessor(); + } + + } + + @Configuration + @EnableStateMachine(name = {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, "fooMachine"}) + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .guard(testGuard()) + .action(testAction()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java new file mode 100644 index 00000000..071811f8 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015 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 + * + * http://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.config; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.EnumSet; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * Tests for state machine configuration. + * + * @author Janne Valkealahti + * + */ +public class ConfigurationTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testStates() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + TestAction testAction = ctx.getBean("testAction", TestAction.class); + TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class); + assertThat(testAction, notNullValue()); + assertThat(testGuard, notNullValue()); + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .guard(testGuard()) + .action(testAction()); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java new file mode 100644 index 00000000..2b360ae1 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/ComplexAnnotationConfigurationTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanB; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter; +import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for generic javaconfig builder/configurer concepts. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class ComplexAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSimpleConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("complexConfig")); + ComplexTestConfig config = ctx.getBean("complexConfig", ComplexTestConfig.class); + assertThat(config.complexData, notNullValue()); + assertThat(config.complexData, is("complexData")); + + assertThat(config.complexProperties, notNullValue()); + assertThat(config.complexProperties.getProperty("complexKey1"), notNullValue()); + assertThat(config.complexProperties.getProperty("complexKey1"), is("complexValue1")); + + assertThat(config.complexBeanA, notNullValue()); + assertThat(config.complexBeanA.dataA, notNullValue()); + assertThat(config.complexBeanA.resources, notNullValue()); + + assertThat(config.complexBeanA.dataA, is("complexDataA")); + assertThat(config.complexBeanA.resources.size(), is(2)); + Iterator iterator = config.complexBeanA.resources.iterator(); + String fileName1 = iterator.next().getFilename(); + String fileName2 = iterator.next().getFilename(); + String[] fileNames = new String[2]; + fileNames[0] = fileName1.equals("complexResourceA1") ? fileName1 : fileName2; + fileNames[1] = fileName2.equals("complexResourceA2") ? fileName2 : fileName1; + assertThat(fileNames[0], is("complexResourceA1")); + assertThat(fileNames[1], is("complexResourceA2")); + + assertTrue(ctx.containsBean("complexConfigData")); + assertTrue(ctx.containsBean("complexConfigBeanB")); + ComplexTestConfigBeanB beanB = ctx.getBean("complexConfigBeanB", ComplexTestConfigBeanB.class); + assertThat(beanB.dataB, is("complexDataB")); + assertThat(beanB.dataBB, is("complexDataBB")); + } + + @Configuration + @EnableComplexTest + static class Config extends ComplexTestConfigurerAdapter { + + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("complexKey1", "complexValue1"); + } + + @Override + public void configure(ComplexTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("complexResourceA1") + .resource("complexResourceA2") + .and() + .setData("complexDataA"); + } + + @Override + public void configure(ComplexTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("complexDataB") + .setDataBB("complexDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java new file mode 100644 index 00000000..7da317e5 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/DependencyBean.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; + +public class DependencyBean { + + private SimpleTestConfigBeanB beanB; + + public SimpleTestConfigBeanB getBeanB() { + return beanB; + } + + public void setBeanB(SimpleTestConfigBeanB beanB) { + this.beanB = beanB; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java new file mode 100644 index 00000000..5d5c2e28 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MixedAnnotationConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter; +import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class MixedAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig simpleConfig = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(simpleConfig.simpleData, notNullValue()); + assertThat(simpleConfig.simpleData, is("simpleData")); + + assertThat(simpleConfig.simpleProperties, notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertTrue(ctx.containsBean("complexConfig")); + ComplexTestConfig complexConfig = ctx.getBean("complexConfig", ComplexTestConfig.class); + assertThat(complexConfig.complexData, notNullValue()); + assertThat(complexConfig.complexData, is("complexData")); + assertThat(complexConfig.complexProperties, notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), is("complexValue1")); + + assertThat(complexConfig.simpleTestConfig, notNullValue()); + assertThat(complexConfig.simpleTestConfig.simpleData, notNullValue()); + assertThat(complexConfig.simpleTestConfig.simpleData, is("simpleData")); + + } + + @Configuration + @EnableSimpleTest + static class SimpleConfig extends SimpleTestConfigurerAdapter { + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + } + + + @Configuration + @EnableComplexTest + static class ComplexConfig extends ComplexTestConfigurerAdapter { + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("complexKey1", "complexValue1"); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java new file mode 100644 index 00000000..2e17e2b1 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/MultipleAnnotationConfigurationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter; +import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class MultipleAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig simpleConfig = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(simpleConfig.simpleData, notNullValue()); + assertThat(simpleConfig.simpleData, is("simpleData")); + + assertThat(simpleConfig.simpleProperties, notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertTrue(ctx.containsBean("complexConfig")); + ComplexTestConfig complexConfig = ctx.getBean("complexConfig", ComplexTestConfig.class); + assertThat(complexConfig.complexData, notNullValue()); + assertThat(complexConfig.complexData, is("complexData")); + + assertThat(complexConfig.complexProperties, notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), notNullValue()); + assertThat(complexConfig.complexProperties.getProperty("complexKey1"), is("complexValue1")); + + } + + @Configuration + @EnableSimpleTest + static class SimpleConfig extends SimpleTestConfigurerAdapter { + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + } + + + @Configuration + @EnableComplexTest + static class ComplexConfig extends ComplexTestConfigurerAdapter { + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("complexKey1", "complexValue1"); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java new file mode 100644 index 00000000..359ca809 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest2; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for generic javaconfig builder/configurer concepts. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class SimpleAnnotationConfiguration2Tests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSimpleConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig config = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(config.simpleData, notNullValue()); + assertThat(config.simpleData, is("simpleData")); + + assertThat(config.simpleProperties, notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertThat(config.simpleBeanA, notNullValue()); + assertThat(config.simpleBeanA.dataA, notNullValue()); + assertThat(config.simpleBeanA.resources, notNullValue()); + + assertThat(config.simpleBeanA.dataA, is("simpleDataA")); + assertThat(config.simpleBeanA.resources.size(), is(2)); + Iterator iterator = config.simpleBeanA.resources.iterator(); + String fileName1 = iterator.next().getFilename(); + String fileName2 = iterator.next().getFilename(); + String[] fileNames = new String[2]; + fileNames[0] = fileName1.equals("simpleResourceA1") ? fileName1 : fileName2; + fileNames[1] = fileName2.equals("simpleResourceA2") ? fileName2 : fileName1; + assertThat(fileNames[0], is("simpleResourceA1")); + assertThat(fileNames[1], is("simpleResourceA2")); + +// assertTrue(ctx.containsBean("simpleConfigData")); +// assertTrue(ctx.containsBean("simpleConfigBeanB")); +// SimpleTestConfigBeanB beanB = ctx.getBean("simpleConfigBeanB", SimpleTestConfigBeanB.class); +// assertThat(beanB.dataB, is("simpleDataB")); +// assertThat(beanB.dataBB, is("simpleDataBB")); + } + + @Configuration + @EnableSimpleTest2 + static class Config extends SimpleTestConfigurerAdapter { + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + + @Override + public void configure(SimpleTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("simpleResourceA1") + .resource("simpleResourceA2") + .and() + .setData("simpleDataA"); + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("simpleDataB") + .setDataBB("simpleDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java new file mode 100644 index 00000000..4f3091da --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfigurationTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for generic javaconfig builder/configurer concepts. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class SimpleAnnotationConfigurationTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSimpleConfig() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + SimpleTestConfig config = ctx.getBean("simpleConfig", SimpleTestConfig.class); + assertThat(config.simpleData, notNullValue()); + assertThat(config.simpleData, is("simpleData")); + + assertThat(config.simpleProperties, notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), notNullValue()); + assertThat(config.simpleProperties.getProperty("simpleKey1"), is("simpleValue1")); + + assertThat(config.simpleBeanA, notNullValue()); + assertThat(config.simpleBeanA.dataA, notNullValue()); + assertThat(config.simpleBeanA.resources, notNullValue()); + + assertThat(config.simpleBeanA.dataA, is("simpleDataA")); + assertThat(config.simpleBeanA.resources.size(), is(2)); + Iterator iterator = config.simpleBeanA.resources.iterator(); + String fileName1 = iterator.next().getFilename(); + String fileName2 = iterator.next().getFilename(); + String[] fileNames = new String[2]; + fileNames[0] = fileName1.equals("simpleResourceA1") ? fileName1 : fileName2; + fileNames[1] = fileName2.equals("simpleResourceA2") ? fileName2 : fileName1; + assertThat(fileNames[0], is("simpleResourceA1")); + assertThat(fileNames[1], is("simpleResourceA2")); + + assertTrue(ctx.containsBean("simpleConfigData")); + assertTrue(ctx.containsBean("simpleConfigBeanB")); + SimpleTestConfigBeanB beanB = ctx.getBean("simpleConfigBeanB", SimpleTestConfigBeanB.class); + assertThat(beanB.dataB, is("simpleDataB")); + assertThat(beanB.dataBB, is("simpleDataBB")); + } + + @Configuration + @EnableSimpleTest + static class Config extends SimpleTestConfigurerAdapter { + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + + @Override + public void configure(SimpleTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("simpleResourceA1") + .resource("simpleResourceA2") + .and() + .setData("simpleDataA"); + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("simpleDataB") + .setDataBB("simpleDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java new file mode 100644 index 00000000..65a5f083 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/XmlImportDependenciesTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Tests for using java config and importing xml config + * with dependency for beans created from a javaconfig. + * + * @author Janne Valkealahti + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=AnnotationConfigContextLoader.class) +public class XmlImportDependenciesTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testDependencyBeanFromXml() throws Exception { + assertNotNull(ctx); + assertTrue(ctx.containsBean("simpleConfig")); + assertTrue(ctx.containsBean("simpleConfigBeanB")); + assertTrue(ctx.containsBean("dependencyBean")); + + DependencyBean dependencyBean = ctx.getBean(DependencyBean.class); + assertThat(dependencyBean, notNullValue()); + assertThat(dependencyBean.getBeanB(), notNullValue()); + } + + @Configuration + @EnableSimpleTest + @ImportResource("org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml") + static class Config extends SimpleTestConfigurerAdapter { + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + config + .withProperties() + .property("simpleKey1", "simpleValue1"); + } + + @Override + public void configure(SimpleTestConfigBeanABuilder beanA) throws Exception { + beanA + .withResources() + .resource("simpleResourceA1") + .resource("simpleResourceA2") + .and() + .setData("simpleDataA"); + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer beanB) throws Exception { + beanB + .setData("simpleDataB") + .setDataBB("simpleDataBB") + .withResources().and(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java new file mode 100644 index 00000000..57c638e7 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfig.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; + +/** + * Main pojo used to collect together configs. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfig { + + public String complexData; + public Properties complexProperties; + public ComplexTestConfigBeanA complexBeanA; + public ComplexTestConfigBeanB complexBeanB; + public SimpleTestConfig simpleTestConfig; + + public ComplexTestConfig(String config, Properties properties) { + complexData = config; + complexProperties = properties; + } + + public void setSimpleBeanB(ComplexTestConfigBeanB complexBeanB) { + this.complexBeanB = complexBeanB; + } + + public void setSimpleBeanA(ComplexTestConfigBeanA complexBeanA) { + this.complexBeanA = complexBeanA; + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java new file mode 100644 index 00000000..1778c8c9 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanA.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import java.util.Set; + +import org.springframework.core.io.Resource; + +/** + * Simple bean storing a set of resources and data. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanA { + + public String dataA; + public Set resources; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java new file mode 100644 index 00000000..f37ac44d --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanABuilder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurerAware; + +/** + * {@link AnnotationBuilder} for {@link ComplexTestConfigBeanA}. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanABuilder + extends AbstractConfiguredAnnotationBuilder + implements ResourceConfigurerAware { + + private String data; + private Set resources = new HashSet(); + + @Override + protected ComplexTestConfigBeanA performBuild() throws Exception { + ComplexTestConfigBeanA bean = new ComplexTestConfigBeanA(); + bean.dataA = data; + bean.resources = resources; + return bean; + } + + @Override + public void configureResources(Set resources) { + this.resources.addAll(resources); + } + + public Set getResources() { + return resources; + } + + public ComplexTestConfigBeanABuilder setData(String data) { + this.data = data; + return this; + } + + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java new file mode 100644 index 00000000..13a1f19f --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanB.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +/** + * Simple bean storing data. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanB { + + public String dataB; + public String dataBB; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java new file mode 100644 index 00000000..2853c30b --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +/** + * {@link AnnotationBuilder} for {@link ComplexTestConfigBeanB}. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBeanBBuilder + extends AbstractConfiguredAnnotationBuilder + implements ComplexTestConfigBeanBConfigurer { + + private String dataB; + private String dataBB; + + @Override + protected ComplexTestConfigBeanB performBuild() throws Exception { + ComplexTestConfigBeanB bean = new ComplexTestConfigBeanB(); + bean.dataB = dataB; + bean.dataBB = dataBB; + return bean; + } + + @Override + public ComplexTestConfigBeanBConfigurer setData(String data) { + this.dataB = data; + return this; + } + + @Override + public ComplexTestConfigBeanBConfigurer setDataBB(String data) { + this.dataBB = data; + return this; + } + + @Override + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java new file mode 100644 index 00000000..5a5bfd68 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBeanBConfigurer.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +public interface ComplexTestConfigBeanBConfigurer { + + ComplexTestConfigBeanBConfigurer setData(String data); + ComplexTestConfigBeanBConfigurer setDataBB(String data); + ResourceConfigurer withResources() throws Exception; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java new file mode 100644 index 00000000..9004aa72 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultPropertiesConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.PropertiesConfigurerAware; + + +/** + * {@link AnnotationBuilder} for {@link ComplexTestConfig}. + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigBuilder extends AbstractConfiguredAnnotationBuilder + implements PropertiesConfigurerAware { + + private final Properties properties = new Properties(); + + @Override + protected ComplexTestConfig performBuild() throws Exception { + ComplexTestConfig bean = new ComplexTestConfig("complexData", properties); + bean.complexBeanA = getSharedObject(ComplexTestConfigBeanABuilder.class).build(); + bean.complexBeanB = getSharedObject(ComplexTestConfigBeanBBuilder.class).build(); + return bean; + } + + @Override + public void configureProperties(Properties properties) { + getProperties().putAll(properties); + } + + public Properties getProperties() { + return properties; + } + + public DefaultPropertiesConfigurer withProperties() throws Exception { + return getOrApply(new DefaultPropertiesConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java new file mode 100644 index 00000000..45d36a87 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.AbstractAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; +import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; + +/** + * @{@link Configuration} which is imported from @{@ EnableSimpleTest}. + * + * @author Janne Valkealahti + * + */ +@Configuration +public class ComplexTestConfiguration extends AbstractAnnotationConfiguration { + + ComplexTestConfigBuilder builder = new ComplexTestConfigBuilder(); + + @Autowired(required=false) + @Qualifier("simpleConfig") + SimpleTestConfig simpleTestConfig; + + @Bean(name="complexConfig") + public ComplexTestConfig complexTestConfig() { + ComplexTestConfig config = builder.getOrBuild(); + config.simpleTestConfig = simpleTestConfig; + return config; + } + + @Bean(name="complexConfigData") + public String complexTestConfigData() { + ComplexTestConfig config = builder.getOrBuild(); + return config.complexData; + } + + @Bean(name="complexConfigBeanB") + public ComplexTestConfigBeanB complexTestConfigBeanB() { + ComplexTestConfig config = builder.getOrBuild(); + return config.complexBeanB; + } + + @Override + protected void onConfigurers(List> configurers) throws Exception { + for (AnnotationConfigurer configurer : configurers) { + if (configurer.isAssignable(builder)) { + builder.apply(configurer); + } + } + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java new file mode 100644 index 00000000..20ed3e66 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +/** + * Example of an interface used in {@link ComplexTestConfigurerAdapter}. + * + * @author Janne Valkealahti + * + */ +public interface ComplexTestConfigurer extends AnnotationConfigurer { + + void configure(ComplexTestConfigBeanABuilder a) throws Exception; + + void configure(ComplexTestConfigBeanBConfigurer b) throws Exception; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java new file mode 100644 index 00000000..87958cc3 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/ComplexTestConfigurerAdapter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; + +/** + * Generic adapter example which user would extend + * in @{@link org.springframework.context.annotation.Configuration} + * + * @author Janne Valkealahti + * + */ +public class ComplexTestConfigurerAdapter implements ComplexTestConfigurer { + + private ComplexTestConfigBeanABuilder beanABuilder; + private ComplexTestConfigBeanBBuilder beanBBuilder; + + @Override + public final void init(ComplexTestConfigBuilder config) throws Exception { + config.setSharedObject(String.class, "complexSharedData"); + config.setSharedObject(ComplexTestConfigBeanABuilder.class, getSimpleTestConfigBeanABuilder()); + config.setSharedObject(ComplexTestConfigBeanBBuilder.class, getSimpleTestConfigBeanBBuilder()); + } + + @Override + public void configure(ComplexTestConfigBuilder config) throws Exception { + } + + @Override + public void configure(ComplexTestConfigBeanABuilder a) throws Exception { + } + + @Override + public void configure(ComplexTestConfigBeanBConfigurer b) throws Exception { + } + + protected final ComplexTestConfigBeanBBuilder getSimpleTestConfigBeanBBuilder() throws Exception { + if (beanBBuilder != null) { + return beanBBuilder; + } + beanBBuilder = new ComplexTestConfigBeanBBuilder(); + configure(beanBBuilder); + return beanBBuilder; + } + + protected final ComplexTestConfigBeanABuilder getSimpleTestConfigBeanABuilder() throws Exception { + if (beanABuilder != null) { + return beanABuilder; + } + beanABuilder = new ComplexTestConfigBeanABuilder(); + configure(beanABuilder); + return beanABuilder; + } + + @Override + public boolean isAssignable(AnnotationBuilder builder) { + return builder instanceof ComplexTestConfigBuilder; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java new file mode 100644 index 00000000..c0da46f5 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/complex/EnableComplexTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.complex; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({ComplexTestConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableComplexTest { +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java new file mode 100644 index 00000000..8422d75d --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({SimpleTestConfiguration.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableSimpleTest { +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java new file mode 100644 index 00000000..1493c1fd --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/EnableSimpleTest2.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration; + +/** + * Example annotation which imports @{@link Configuration}s. + * + * @author Janne Valkealahti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@EnableAnnotationConfiguration +@Import({SimpleTestConfiguration2.class,ObjectPostProcessorConfiguration.class}) +public @interface EnableSimpleTest2 { + + String[] name() default {"simpleConfig"}; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java new file mode 100644 index 00000000..c1d255f1 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.util.Properties; + +/** + * Main pojo used to collect together configs. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfig { + + public String simpleData; + public Properties simpleProperties; + public SimpleTestConfigBeanA simpleBeanA; + public SimpleTestConfigBeanB simpleBeanB; + + public SimpleTestConfig(String config, Properties properties) { + simpleData = config; + simpleProperties = properties; + } + + public void setSimpleBeanB(SimpleTestConfigBeanB simpleBeanB) { + this.simpleBeanB = simpleBeanB; + } + + public void setSimpleBeanA(SimpleTestConfigBeanA simpleBeanA) { + this.simpleBeanA = simpleBeanA; + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java new file mode 100644 index 00000000..29024a09 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanA.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.util.Set; + +import org.springframework.core.io.Resource; + +/** + * Simple bean storing a set of resources and data. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanA { + + public String dataA; + public Set resources; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java new file mode 100644 index 00000000..6ec53321 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanABuilder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.io.Resource; +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurerAware; + +/** + * {@link AnnotationBuilder} for {@link SimpleTestConfigBeanA}. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanABuilder + extends AbstractConfiguredAnnotationBuilder + implements ResourceConfigurerAware { + + private String data; + private Set resources = new HashSet(); + + @Override + protected SimpleTestConfigBeanA performBuild() throws Exception { + SimpleTestConfigBeanA bean = new SimpleTestConfigBeanA(); + bean.dataA = data; + bean.resources = resources; + return bean; + } + + @Override + public void configureResources(Set resources) { + this.resources.addAll(resources); + } + + public Set getResources() { + return resources; + } + + public SimpleTestConfigBeanABuilder setData(String data) { + this.data = data; + return this; + } + + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java new file mode 100644 index 00000000..879a48e6 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanB.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +/** + * Simple bean storing data. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanB { + + public String dataB; + public String dataBB; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java new file mode 100644 index 00000000..b72c5605 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultResourceConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +/** + * {@link AnnotationBuilder} for {@link SimpleTestConfigBeanB}. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBeanBBuilder + extends AbstractConfiguredAnnotationBuilder + implements SimpleTestConfigBeanBConfigurer { + + private String dataB; + private String dataBB; + + @Override + protected SimpleTestConfigBeanB performBuild() throws Exception { + SimpleTestConfigBeanB bean = new SimpleTestConfigBeanB(); + bean.dataB = dataB; + bean.dataBB = dataBB; + return bean; + } + + @Override + public SimpleTestConfigBeanBConfigurer setData(String data) { + this.dataB = data; + return this; + } + + @Override + public SimpleTestConfigBeanBConfigurer setDataBB(String data) { + this.dataBB = data; + return this; + } + + @Override + public ResourceConfigurer withResources() throws Exception { + return getOrApply(new DefaultResourceConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java new file mode 100644 index 00000000..0d7b8204 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBeanBConfigurer.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.configurers.ResourceConfigurer; + +public interface SimpleTestConfigBeanBConfigurer { + + SimpleTestConfigBeanBConfigurer setData(String data); + SimpleTestConfigBeanBConfigurer setDataBB(String data); + ResourceConfigurer withResources() throws Exception; + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java new file mode 100644 index 00000000..2f47d69f --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.util.Properties; + +import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; +import org.springframework.statemachine.config.common.annotation.configurers.DefaultPropertiesConfigurer; +import org.springframework.statemachine.config.common.annotation.configurers.PropertiesConfigurerAware; + + +/** + * {@link AnnotationBuilder} for {@link SimpleTestConfig}. + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigBuilder extends AbstractConfiguredAnnotationBuilder + implements PropertiesConfigurerAware { + + private final Properties properties = new Properties(); + + @Override + protected SimpleTestConfig performBuild() throws Exception { + SimpleTestConfig bean = new SimpleTestConfig("simpleData", properties); + bean.simpleBeanA = getSharedObject(SimpleTestConfigBeanABuilder.class).build(); + bean.simpleBeanB = getSharedObject(SimpleTestConfigBeanBBuilder.class).build(); + return bean; + } + + @Override + public void configureProperties(Properties properties) { + getProperties().putAll(properties); + } + + public Properties getProperties() { + return properties; + } + + public DefaultPropertiesConfigurer withProperties() throws Exception { + return getOrApply(new DefaultPropertiesConfigurer()); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java new file mode 100644 index 00000000..d726bf0c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.AbstractAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +/** + * @{@link Configuration} which is imported from @{@ EnableSimpleTest}. + * + * @author Janne Valkealahti + * + */ +@Configuration +public class SimpleTestConfiguration extends AbstractAnnotationConfiguration { + + private final SimpleTestConfigBuilder builder = new SimpleTestConfigBuilder(); + + @Bean(name="simpleConfig") + public SimpleTestConfig simpleTestConfig() { + SimpleTestConfig config = builder.getOrBuild(); + return config; + } + + @Bean(name="simpleConfigData") + public String simpleTestConfigData() { + SimpleTestConfig config = builder.getOrBuild(); + return config.simpleData; + } + + @Bean(name="simpleConfigBeanB") + public SimpleTestConfigBeanB simpleTestConfigBeanB() { + SimpleTestConfig config = builder.getOrBuild(); + return config.simpleBeanB; + } + + @Override + protected void onConfigurers(List> configurers) throws Exception { + for (AnnotationConfigurer configurer : configurers) { + if (configurer.isAssignable(builder)) { + builder.apply(configurer); + } + } + } + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java new file mode 100644 index 00000000..269a15af --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfiguration2.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import java.lang.annotation.Annotation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +@Configuration +public class SimpleTestConfiguration2 extends AbstractImportingAnnotationConfiguration { + + private final SimpleTestConfigBuilder builder = new SimpleTestConfigBuilder(); + + @Override + protected BeanDefinition buildBeanDefinition() throws Exception { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(SimpleTestConfigDelegatingFactoryBean.class); + beanDefinitionBuilder.addConstructorArgValue(builder); + return beanDefinitionBuilder.getBeanDefinition(); + } + + @Override + protected Class getAnnotation() { + return EnableSimpleTest2.class; + } + + private static class SimpleTestConfigDelegatingFactoryBean extends BeanDelegatingFactoryBean { + + public SimpleTestConfigDelegatingFactoryBean(SimpleTestConfigBuilder builder) { + super(builder); + } + + @Override + public Class getObjectType() { + return SimpleTestConfig.class; + } + + @Override + public void afterPropertiesSet() throws Exception { + for (AnnotationConfigurer configurer : getConfigurers()) { + if (configurer.isAssignable(getBuilder())) { + getBuilder().apply(configurer); + } + } + setObject(getBuilder().getOrBuild()); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java new file mode 100644 index 00000000..1e309cb4 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; + +/** + * Example of an interface used in {@link SimpleTestConfigurerAdapter}. + * + * @author Janne Valkealahti + * + */ +public interface SimpleTestConfigurer extends AnnotationConfigurer { + + void configure(SimpleTestConfigBeanABuilder a) throws Exception; + + void configure(SimpleTestConfigBeanBConfigurer b) throws Exception; + +} \ No newline at end of file diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java new file mode 100644 index 00000000..7e93c9e4 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/simple/SimpleTestConfigurerAdapter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 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 + * + * http://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.config.common.annotation.simple; + +import org.springframework.statemachine.config.common.annotation.AnnotationBuilder; + +/** + * Generic adapter example which user would extend + * in @{@link org.springframework.context.annotation.Configuration} + * + * @author Janne Valkealahti + * + */ +public class SimpleTestConfigurerAdapter implements SimpleTestConfigurer { + + private SimpleTestConfigBeanABuilder beanABuilder; + private SimpleTestConfigBeanBBuilder beanBBuilder; + + @Override + public final void init(SimpleTestConfigBuilder config) throws Exception { + config.setSharedObject(String.class, "simpleSharedData"); + config.setSharedObject(SimpleTestConfigBeanABuilder.class, getSimpleTestConfigBeanABuilder()); + config.setSharedObject(SimpleTestConfigBeanBBuilder.class, getSimpleTestConfigBeanBBuilder()); + } + + @Override + public void configure(SimpleTestConfigBuilder config) throws Exception { + } + + @Override + public void configure(SimpleTestConfigBeanABuilder a) throws Exception { + } + + @Override + public void configure(SimpleTestConfigBeanBConfigurer b) throws Exception { + } + + protected final SimpleTestConfigBeanBBuilder getSimpleTestConfigBeanBBuilder() throws Exception { + if (beanBBuilder != null) { + return beanBBuilder; + } + beanBBuilder = new SimpleTestConfigBeanBBuilder(); + configure(beanBBuilder); + return beanBBuilder; + } + + protected final SimpleTestConfigBeanABuilder getSimpleTestConfigBeanABuilder() throws Exception { + if (beanABuilder != null) { + return beanABuilder; + } + beanABuilder = new SimpleTestConfigBeanABuilder(); + configure(beanABuilder); + return beanABuilder; + } + + @Override + public boolean isAssignable(AnnotationBuilder builder) { + return builder instanceof SimpleTestConfigBuilder; + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java new file mode 100644 index 00000000..cf959bbc --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java @@ -0,0 +1,172 @@ +/* + * Copyright 2015 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 + * + * http://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.guard; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.AbstractStateMachineTests.TestAction; +import org.springframework.statemachine.AbstractStateMachineTests.TestEvents; +import org.springframework.statemachine.AbstractStateMachineTests.TestGuard; +import org.springframework.statemachine.AbstractStateMachineTests.TestStates; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * Tests for state machine guards. + * + * @author Janne Valkealahti + * + */ +public class GuardTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testGuardEvaluated() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class); + TestAction testAction = ctx.getBean("testAction", TestAction.class); + assertThat(testGuard, notNullValue()); + assertThat(testAction, notNullValue()); + + machine.start(); + machine.sendEvent(TestEvents.E1); + assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true)); + + ctx.close(); + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testGuardDenyAction() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config2.class); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class); + TestAction testAction = ctx.getBean("testAction", TestAction.class); + assertThat(testGuard, notNullValue()); + assertThat(testAction, notNullValue()); + + machine.start(); + assertThat(machine.getState().getId(), is(TestStates.S1)); + + machine.sendEvent(TestEvents.E1); + assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(false)); + assertThat(machine.getState().getId(), is(TestStates.S1)); + + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(testAction()) + .guard(testGuard()); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(true); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(testAction()) + .guard(testGuard()); + } + + @Bean + public TestGuard testGuard() { + return new TestGuard(false); + } + + @Bean + public TestAction testAction() { + return new TestAction(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java new file mode 100644 index 00000000..b0a52dd2 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 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 + * + * http://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.guard; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.support.DefaultStateContext; + +/** + * Tests for using spel expressions in guards. + * + * @author Janne Valkealahti + * + */ +public class SpelExpressionGuardTests extends AbstractStateMachineTests { + + @Test + public void testSimpleSpel() { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + Expression expression = parser.parseExpression("messageHeaders.get('foo')=='bar'"); + SpelExpressionGuard guard = new SpelExpressionGuard(expression); + Map map = new HashMap(); + map.put("foo", "bar"); + MessageHeaders headers = new MessageHeaders(map); + DefaultStateContext stateContext = new DefaultStateContext(headers, null); + + assertThat(guard.evaluate(stateContext), is(true)); + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testGuardDenyStateChange() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getId(), is(TestStates.S1)); + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .guardExpression("false"); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java new file mode 100644 index 00000000..fdddac9c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java @@ -0,0 +1,173 @@ +/* + * Copyright 2015 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 + * + * http://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.listener; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.state.State; + +/** + * Tests for state machine listener functionality. + * + * @author Janne Valkealahti + * + */ +public class ListenerTests extends AbstractStateMachineTests { + + @Test + public void testStateEvents() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + @SuppressWarnings("unchecked") + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + TestStateMachineListener listener = new TestStateMachineListener(); + machine.addStateListener(listener); + + assertThat(machine, notNullValue()); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build()); + assertThat(listener.states.size(), is(1)); + assertThat(listener.states.get(0).from.getId(), is(TestStates.S1)); + assertThat(listener.states.get(0).to.getId(), is(TestStates.S2)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build()); + assertThat(listener.states.size(), is(2)); + assertThat(listener.states.get(1).from.getId(), is(TestStates.S2)); + assertThat(listener.states.get(1).to.getId(), is(TestStates.S3)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build()); + assertThat(listener.states.size(), is(2)); + + ctx.close(); + } + + private static class LoggingAction implements Action { + + private static final Log log = LogFactory.getLog(LoggingAction.class); + + private String message; + + public LoggingAction(String message) { + this.message = message; + } + + @Override + public void execute(StateContext context) { + log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo")); + } + + } + + private static class TestStateMachineListener implements StateMachineListener, TestEvents> { + + ArrayList states = new ArrayList(); + + @Override + public void stateChanged(State from, State to) { + states.add(new Holder(from, to)); + } + + static class Holder { + State from; + State to; + public Holder(State from, State to) { + this.from = from; + this.to = to; + } + } + + } + + @Configuration + @EnableStateMachine + static class Config extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2) + .state(TestStates.S3, TestEvents.E4) + .state(TestStates.S4); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .action(loggingAction()) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E3) + .action(loggingAction()) + .and() + .withExternal() + .source(TestStates.S4) + .target(TestStates.S3) + .event(TestEvents.E4) + .action(loggingAction()); + } + + @Bean + public LoggingAction loggingAction() { + return new LoggingAction("as bean"); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java new file mode 100644 index 00000000..ba37a50c --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015 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 + * + * http://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.state; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * Tests for state entry and exit actions. + * + * @author Janne Valkealahti + * + */ +public class StateActionTests extends AbstractStateMachineTests { + + @Test + @SuppressWarnings("unchecked") + public void testStateEntryExit() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + TestExitAction testExitAction = ctx.getBean("testExitAction", TestExitAction.class); + TestEntryAction testEntryAction = ctx.getBean("testEntryAction", TestEntryAction.class); + assertThat(testExitAction, notNullValue()); + assertThat(testEntryAction, notNullValue()); + + machine.start(); + machine.sendEvent(TestEvents.E1); + assertThat(testExitAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(testEntryAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true)); + + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + Collection entryActions = Arrays.asList(testEntryAction()); + Collection exitActions = Arrays.asList(testExitAction()); + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1, null, exitActions) + .state(TestStates.S2, entryActions, null); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + + @Bean + public Action testEntryAction() { + return new TestEntryAction(); + } + + @Bean + public Action testExitAction() { + return new TestExitAction(); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java new file mode 100644 index 00000000..eacf4e1f --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2015 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 + * + * http://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.transition; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +/** + * Tests for state machine transitions. + * + * @author Janne Valkealahti + * + */ +public class TransitionTests extends AbstractStateMachineTests { + + @SuppressWarnings({ "unchecked" }) + @Test + public void testTriggerlessTransition() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config1.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getId(), is(TestStates.S3)); + ctx.close(); + + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testInternalTransition() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config2.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + + TestExitAction testExitAction = ctx.getBean("testExitAction", TestExitAction.class); + TestEntryAction testEntryAction = ctx.getBean("testEntryAction", TestEntryAction.class); + TestAction externalTestAction = ctx.getBean("externalTestAction", TestAction.class); + TestAction internalTestAction = ctx.getBean("internalTestAction", TestAction.class); + + assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + + machine.sendEvent(TestEvents.E1); + assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(internalTestAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + + machine.sendEvent(TestEvents.E2); + assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(externalTestAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + + assertThat(machine.getState().getId(), is(TestStates.S2)); + ctx.close(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3); + } + + } + + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + Collection entryActions = Arrays.asList(testEntryAction()); + Collection exitActions = Arrays.asList(testExitAction()); + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1, null, exitActions) + .state(TestStates.S2, entryActions, null); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withInternal() + .source(TestStates.S1) + .event(TestEvents.E1) + .action(internalTestAction()) + .and() + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E2) + .action(externalTestAction()); + } + + @Bean + public Action testEntryAction() { + return new TestEntryAction(); + } + + @Bean + public Action testExitAction() { + return new TestExitAction(); + } + + @Bean + public Action externalTestAction() { + return new TestAction(); + } + + @Bean + public Action internalTestAction() { + return new TestAction(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml b/spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml new file mode 100644 index 00000000..e651bb31 --- /dev/null +++ b/spring-statemachine-core/src/test/resources/org/springframework/statemachine/config/common/annotation/XmlImportDependencies.xml @@ -0,0 +1,10 @@ + + + + + + + +