├── .gitignore ├── .mvn ├── jvm.config ├── maven.config └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.adoc ├── composedtaskrunner-task-app-dependencies └── pom.xml ├── mvnw ├── mvnw.cmd ├── pom.xml └── spring-cloud-starter-task-composedtaskrunner ├── README.adoc ├── images ├── basicsequence.png ├── basicsplit.png ├── basicsplitwithsequence.png ├── basictransition.png ├── basictransitionwithsequence.png ├── basictransitionwithwildcard.png └── samejobsequence.png ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── springframework │ │ └── cloud │ │ └── task │ │ └── app │ │ └── composedtaskrunner │ │ ├── ComposedBatchConfigurer.java │ │ ├── ComposedRunnerJobFactory.java │ │ ├── ComposedRunnerVisitor.java │ │ ├── ComposedTaskRunner.java │ │ ├── ComposedTaskRunnerConfiguration.java │ │ ├── ComposedTaskRunnerStepFactory.java │ │ ├── ComposedTaskStepExecutionListener.java │ │ ├── DataFlowConfiguration.java │ │ ├── StepBeanDefinitionRegistrar.java │ │ ├── TaskLauncherTasklet.java │ │ ├── properties │ │ └── ComposedTaskProperties.java │ │ └── support │ │ ├── OnOAuth2ClientCredentialsEnabled.java │ │ └── TaskExecutionTimeoutException.java └── resources │ ├── META-INF │ ├── dataflow-configuration-metadata-whitelist.properties │ └── spring-configuration-metadata-whitelist.properties │ └── banner.txt └── test └── java └── org └── springframework └── cloud └── task └── app └── composedtaskrunner ├── ComposedRunnerVisitorTests.java ├── ComposedTaskRunnerConfigurationJobIncrementerTests.java ├── ComposedTaskRunnerConfigurationNoPropertiesTests.java ├── ComposedTaskRunnerConfigurationWithPropertiesTests.java ├── ComposedTaskRunnerStepFactoryTests.java ├── ComposedTaskStepExecutionListenerTests.java ├── TaskLauncherTaskletTests.java ├── configuration ├── ComposedRunnerVisitorConfiguration.java ├── DataFlowConfigurationTests.java └── DataFlowTestConfiguration.java ├── properties └── ComposedTaskPropertiesTests.java └── support └── OnOAuth2ClientCredentialsEnabledTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | apps/ 2 | /application.yml 3 | /application.properties 4 | asciidoctor.css 5 | *~ 6 | .#* 7 | *# 8 | target/ 9 | build/ 10 | bin/ 11 | _site/ 12 | .classpath 13 | .project 14 | .settings 15 | .springBeans 16 | .DS_Store 17 | *.sw* 18 | *.iml 19 | *.ipr 20 | *.iws 21 | .idea/ 22 | .factorypath 23 | spring-xd-samples/*/xd 24 | dump.rdb 25 | coverage-error.log 26 | .apt_generated 27 | aws.credentials.properties 28 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # composed-task-runner is no longer actively maintained by VMware, Inc. 2 | 3 | # Composed Task Runner Application 4 | 5 | = PLEASE NOTE: This project has been moved to the https://github.com/spring-cloud/spring-cloud-dataflow[Spring Cloud Data Flow Project]. 6 | 7 | 8 | -------------------------------------------------------------------------------- /composedtaskrunner-task-app-dependencies/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.springframework.cloud.task.app 5 | composedtaskrunner-task-app-dependencies 6 | 2.1.5.BUILD-SNAPSHOT 7 | pom 8 | Composed Task Runner Task App Dependencies 9 | Dependencies for Spring Cloud Task Composed Task Runner App Starter 10 | 11 | 12 | spring-cloud-dependencies-parent 13 | org.springframework.cloud 14 | 2.1.11.RELEASE 15 | 16 | 17 | 18 | 19 | 2.1.3.RELEASE 20 | 2.2.1.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.cloud.task.app 27 | spring-cloud-starter-task-composedtaskrunner 28 | ${project.version} 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-starter-task 33 | ${spring-cloud-task.version} 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-dataflow-rest-client 38 | ${spring-cloud-dataflow.version} 39 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-dataflow-core 43 | ${spring-cloud-dataflow.version} 44 | 45 | 46 | 47 | 48 | 49 | spring 50 | 51 | 52 | spring-snapshots 53 | Spring Snapshots 54 | https://repo.spring.io/libs-snapshot-local 55 | 56 | true 57 | 58 | 59 | 60 | spring-milestones 61 | Spring Milestones 62 | https://repo.spring.io/libs-milestone-local 63 | 64 | false 65 | 66 | 67 | 68 | spring-releases 69 | Spring Releases 70 | https://repo.spring.io/release 71 | 72 | false 73 | 74 | 75 | 76 | spring-libs-release 77 | Spring Libs Release 78 | https://repo.spring.io/libs-release 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | spring-snapshots 87 | Spring Snapshots 88 | https://repo.spring.io/libs-snapshot-local 89 | 90 | true 91 | 92 | 93 | 94 | spring-milestones 95 | Spring Milestones 96 | https://repo.spring.io/libs-milestone-local 97 | 98 | false 99 | 100 | 101 | 102 | spring-libs-release 103 | Spring Libs Release 104 | https://repo.spring.io/libs-release 105 | 106 | false 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | echo "Running version check" 230 | VERSION=$( sed '\!//' -e 's!.*$!!' ) 231 | echo "The found version is [${VERSION}]" 232 | 233 | if echo $VERSION | egrep -q 'M|RC'; then 234 | echo Activating \"milestone\" profile for version=\"$VERSION\" 235 | echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" 236 | else 237 | echo Deactivating \"milestone\" profile for version=\"$VERSION\" 238 | echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') 239 | fi 240 | 241 | if echo $VERSION | egrep -q 'RELEASE'; then 242 | echo Activating \"central\" profile for version=\"$VERSION\" 243 | echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pcentral" 244 | else 245 | echo Deactivating \"central\" profile for version=\"$VERSION\" 246 | echo $MAVEN_ARGS | grep -q central && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pcentral//') 247 | fi 248 | 249 | exec "$JAVACMD" \ 250 | $MAVEN_OPTS \ 251 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 252 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 253 | ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" 254 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% 146 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | composedtaskrunner-task-app-starters-build 5 | 2.1.5.BUILD-SNAPSHOT 6 | pom 7 | 8 | 9 | org.springframework.cloud.task.app 10 | task-app-starters-build 11 | 2.1.2.RELEASE 12 | 13 | 14 | 15 | 16 | spring-cloud-starter-task-composedtaskrunner 17 | composedtaskrunner-task-app-dependencies 18 | 19 | 20 | 21 | 5.3.1 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.cloud.task.app 28 | composedtaskrunner-task-app-dependencies 29 | 2.1.5.BUILD-SNAPSHOT 30 | pom 31 | import 32 | 33 | 34 | 35 | 36 | 37 | 38 | spring 39 | 40 | 41 | spring-snapshots 42 | Spring Snapshots 43 | https://repo.spring.io/libs-snapshot-local 44 | 45 | true 46 | 47 | 48 | 49 | spring-milestones 50 | Spring Milestones 51 | https://repo.spring.io/libs-milestone-local 52 | 53 | false 54 | 55 | 56 | 57 | spring-libs-release 58 | Spring Libs Release 59 | https://repo.spring.io/libs-release 60 | 61 | false 62 | 63 | 64 | 65 | 66 | 67 | spring-snapshots 68 | Spring Snapshots 69 | https://repo.spring.io/libs-snapshot-local 70 | 71 | true 72 | 73 | 74 | 75 | spring-milestones 76 | Spring Milestones 77 | https://repo.spring.io/libs-milestone-local 78 | 79 | false 80 | 81 | 82 | 83 | spring-libs-release 84 | Spring Libs Release 85 | https://repo.spring.io/libs-release 86 | 87 | false 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/README.adoc: -------------------------------------------------------------------------------- 1 | //tag::ref-doc[] 2 | :image-root: https://raw.githubusercontent.com/spring-cloud-task-app-starters/composed-task-runner/master/spring-cloud-starter-task-composedtaskrunner/images 3 | 4 | = Composed Task Runner 5 | 6 | A task that executes a tasks in a directed graph as specified by a DSL that is 7 | passed in via the `--graph` command line argument. 8 | 9 | == Overview 10 | The Composed Task Runner parses the graph DSL and for each node in the graph it 11 | will execute a restful call against a specified https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/[Spring Cloud Data Flow] 12 | instance to launch the associated task definition. For each task definition that is executed the 13 | Composed Task Runner will poll the database to verify that the task completed. 14 | Once complete the Composed Task Runner will either continue to the next task in 15 | the graph or fail based on how the DSL specified the sequence of tasks should 16 | be executed. 17 | 18 | == Graph DSL 19 | 20 | The Graph DSL is comprised of Task Definitions that have been defined within 21 | the Spring Cloud Data Flow server referenced by the data-flow-uri 22 | (default: http://localhost:9393). 23 | These definitions can be placed into a derived graph based on a DSL through 24 | the use of sequences, transitions, splits, or a combination therein. 25 | 26 | == Traversing the graph 27 | Composed Task Runner is built using 28 | https://docs.spring.io/spring-batch/reference/html/[Spring Batch] 29 | to execute the directed graph. As such each node in the graph is a 30 | https://docs.spring.io/spring-batch/reference/html/domain.html#domainStep[Step]. 31 | As discussed in the overview, each step in the graph will post a request to a 32 | Spring Cloud Data Flow Server to execute a task definition. If the task launched by 33 | the step fails to complete within the time specified by the `maxWaitTime` 34 | property, a 35 | org.springframework.cloud.task.app.composedtaskrunner.support.TimeoutException 36 | will be thrown. Once task launched by the step completes, 37 | the ComposedTaskRunner will set the `ExitStatus` of that step based on the following rules: 38 | 39 | * If the `TaskExecution` has an `ExitMessage` that will be used as the `ExitStatus` 40 | * If no `ExitMessage` is present and the `ExitCode` is set to 0 then the `ExitStatus` 41 | for the step will be `COMPLETED`. 42 | * If no `ExitMessage` is present and the `ExitCode` is set to 1 then the `ExitStatus` 43 | for the step will be `FAILED`. 44 | 45 | If the state of any step in the graph is set to FAILED and is not handled by 46 | the DSL the Directed Graph execution will terminate. 47 | 48 | === Sequences 49 | The Composed Task Runner supports the ability to traverse sequences of task 50 | definitions. This is represented by a task definition name followed by the 51 | `&&` symbol then the next task definition to be launched. 52 | For example if we have tasks AAA, BBB and CCC to be launched in sequence it 53 | will look like this: 54 | ``` 55 | AAA && BBB && CCC 56 | ``` 57 | image::{image-root}/basicsequence.png[basic sequence] 58 | 59 | You can execute the same task multiple times in a sequence. For example: 60 | ``` 61 | AAA && AAA && AAA 62 | ``` 63 | image::{image-root}/samejobsequence.png[basic sequence with repeated job definition] 64 | 65 | If an `ExitStatus` 'FAILED' is returned in a sequence the Composed Task 66 | Runner will terminate. For example if `AAA && BBB && CCC` composed task is 67 | executed and BBB fails. Then CCC will not be launched. 68 | 69 | === Transitions 70 | The Composed Task Runner supports the ability to control what tasks get 71 | executed based on the `ExitStatus` of the previous task. This is 72 | done by specifying `ExitStatus` after the task definition followed by 73 | the `->` operator and the task definition that should be launched based on 74 | the result. For example: 75 | ``` 76 | AAA 'FAILED' -> BBB 'COMPLETED' -> CCC 77 | ``` 78 | image::{image-root}/basictransition.png[basic transition] 79 | 80 | Will launch AAA and if AAA fails then BBB will be launched. Else if AAA 81 | completes successfully then CCC will launch. 82 | 83 | You can also have a sequence that follows a transition. For example: 84 | ``` 85 | AAA 'FAILED' -> BBB && CCC && DDD 86 | ``` 87 | image::{image-root}/basictransitionwithsequence.png[basic transition with sequence] 88 | 89 | Will launch AAA and for any `ExitStatus` that is returned other than 'FAILED' then 90 | CCC && DDD will be launched. However if AAA returns 'FAILED' then BBB will 91 | be launched, but CCC && DDD will not. 92 | 93 | ==== Wildcard 94 | Wildcards are also supported in transitions. 95 | For example: 96 | ``` 97 | AAA 'FAILED' -> BBB '*'->CCC 98 | ``` 99 | image::{image-root}/basictransitionwithwildcard.png[basic transition with wildcard] 100 | 101 | In the case above AAA will launch and any `ExitStatus` other than FAILED will 102 | launch CCC. 103 | 104 | === Splits 105 | Allows a user to execute tasks in parallel. 106 | For example: 107 | ``` 108 | 109 | ``` 110 | image::{image-root}/basicsplit.png[basic split] 111 | 112 | Will launch AAA, BBB and CCC in parallel. When launching splits as a part of a 113 | composed task all elements of the split must finish successfully before the 114 | next task definition can be launched for example: 115 | ``` 116 | && DDD && EEE 117 | ``` 118 | image::{image-root}/basicsplitwithsequence.png[basic split with sequence] 119 | 120 | In the case above once AAA, BBB and CCC complete sucessfully then DDD and EEE 121 | will be launched in the sequence enumerated above. However if one of the task 122 | definitions fails in the split then DDD and EEE will not fire. For example if 123 | BBB fails then AAA and CCC will be marked successful and BBB will be marked a 124 | failure and DDD and EEE will not be launched. 125 | 126 | == Options 127 | 128 | // see syntax (soon to be automatically generated) in spring-cloud-stream starters 129 | The **$$ComposedTaskRunner$$** $$task$$ has the following options: 130 | 131 | //tag::configuration-properties[] 132 | $$composed-task-arguments$$:: $$The arguments to be used for each of the tasks.$$ *($$String$$, default: `$$$$`)* 133 | $$composed-task-properties$$:: $$The properties to be used for each of the tasks as well as their deployments.$$ *($$String$$, default: `$$$$`)* 134 | $$dataflow-server-access-token$$:: $$The optional OAuth2 Access Token.$$ *($$String$$, default: `$$$$`)* 135 | $$dataflow-server-password$$:: $$The optional password for the dataflow server that will receive task launch requests. Used to access the the dataflow server using Basic Authentication. Not used if {@link #dataflowServerAccessToken} is set.$$ *($$String$$, default: `$$$$`)* 136 | $$dataflow-server-uri$$:: $$The URI for the dataflow server that will receive task launch requests. Default is http://localhost:9393;$$ *($$URI$$, default: `$$$$`)* 137 | $$dataflow-server-username$$:: $$The optional username for the dataflow server that will receive task launch requests. Used to access the the dataflow server using Basic Authentication. Not used if {@link #dataflowServerAccessToken} is set.$$ *($$String$$, default: `$$$$`)* 138 | $$graph$$:: $$The DSL for the composed task directed graph.$$ *($$String$$, default: `$$$$`)* 139 | $$increment-instance-enabled$$:: $$Allows a single ComposedTaskRunner instance to be re-executed without changing the parameters. Default is false which means a ComposedTaskRunner instance can only be executed once with a given set of parameters, if true it can be re-executed.$$ *($$Boolean$$, default: `$$false$$`)* 140 | $$interval-time-between-checks$$:: $$The amount of time in millis that the ComposedTaskRunner will wait between checks of the database to see if a task has completed.$$ *($$Integer$$, default: `$$10000$$`)* 141 | $$max-wait-time$$:: $$The maximum amount of time in millis that a individual step can run before the execution of the Composed task is failed.$$ *($$Integer$$, default: `$$0$$`)* 142 | $$oauth2-client-credentials-client-id$$:: $$The OAuth2 Client Id (Used for the client credentials grant). If not null, then the following properties are ignored:
  • dataflowServerUsername
  • dataflowServerPassword
  • dataflowServerAccessToken
      $$ *($$String$$, default: `$$$$`)* 143 | $$oauth2-client-credentials-client-secret$$:: $$The OAuth2 Client Secret (Used for the client credentials grant).$$ *($$String$$, default: `$$$$`)* 144 | $$oauth2-client-credentials-scopes$$:: $$OAuth2 Authorization scopes (Used for the client credentials grant).$$ *($$Set$$, default: `$$$$`)* 145 | $$oauth2-client-credentials-token-uri$$:: $$Token URI for the OAuth2 provider (Used for the client credentials grant).$$ *($$String$$, default: `$$$$`)* 146 | $$split-thread-allow-core-thread-timeout$$:: $$Specifies whether to allow split core threads to timeout. Default is false;$$ *($$Boolean$$, default: `$$false$$`)* 147 | $$split-thread-core-pool-size$$:: $$Split's core pool size. Default is 4;$$ *($$Integer$$, default: `$$4$$`)* 148 | $$split-thread-keep-alive-seconds$$:: $$Split's thread keep alive seconds. Default is 60.$$ *($$Integer$$, default: `$$60$$`)* 149 | $$split-thread-max-pool-size$$:: $$Split's maximum pool size. Default is {@code Integer.MAX_VALUE}.$$ *($$Integer$$, default: `$$$$`)* 150 | $$split-thread-queue-capacity$$:: $$Capacity for Split's BlockingQueue. Default is {@code Integer.MAX_VALUE}.$$ *($$Integer$$, default: `$$$$`)* 151 | $$split-thread-wait-for-tasks-to-complete-on-shutdown$$:: $$Whether to wait for scheduled tasks to complete on shutdown, not interrupting running tasks and executing all tasks in the queue. Default is false;$$ *($$Boolean$$, default: `$$false$$`)* 152 | //end::configuration-properties[] 153 | 154 | NOTE: when using the options above as environment variables, remove the `-` 's and capitalize the next character. 155 | For example: `increment-instance-enabled` would be `incrementInstanceEnabled`. 156 | 157 | == Building with Maven 158 | 159 | ``` 160 | $ ./mvnw clean install -PgenerateApps 161 | $ cd apps/composedtaskrunner-task 162 | $ ./mvnw clean package 163 | ``` 164 | 165 | == Example 166 | `java -jar composedtaskrunner-task-{version}.jar --graph=` 167 | 168 | == Contributing 169 | 170 | We welcome contributions! Follow this https://github.com/spring-cloud-task-app-starters/app-starters-release/blob/master/spring-cloud-task-app-starters-docs/src/main/asciidoc/contributing.adoc[link] for more information on how to contribute. 171 | 172 | //end::ref-doc[] 173 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/basicsequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/basicsequence.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/basicsplit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/basicsplit.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/basicsplitwithsequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/basicsplitwithsequence.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/basictransition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/basictransition.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/basictransitionwithsequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/basictransitionwithsequence.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/basictransitionwithwildcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/basictransitionwithwildcard.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/images/samejobsequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-task-app-starters-composed-task-runner/cc8e886a121e77697f544c40e832836e8091dae7/spring-cloud-starter-task-composedtaskrunner/images/samejobsequence.png -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | spring-cloud-starter-task-composedtaskrunner 6 | jar 7 | Spring Cloud Starter Task Composed Task Runner 8 | Contains the app for the Composed Task Starter 9 | 10 | 11 | org.springframework.cloud.task.app 12 | composedtaskrunner-task-app-starters-build 13 | 2.1.5.BUILD-SNAPSHOT 14 | 15 | 16 | 17 | 1.1.4.RELEASE 18 | 5.2.0.RELEASE 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | org.mockito 32 | mockito-core 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-starter-task 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-batch 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-dataflow-rest-client 45 | 46 | 47 | org.springframework.cloud 48 | spring-cloud-dataflow-core 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-configuration-processor 54 | true 55 | 56 | 57 | org.junit.jupiter 58 | junit-jupiter-api 59 | test 60 | 61 | 62 | org.springframework.cloud 63 | spring-cloud-starter-common-security-config-web 64 | ${spring-cloud-common-security-config.version} 65 | 66 | 67 | org.codehaus.jackson 68 | jackson-mapper-asl 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-starter-web 73 | 74 | 75 | 76 | 77 | org.springframework.security 78 | spring-security-oauth2-client 79 | ${spring-security.version} 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.springframework.cloud 87 | spring-cloud-app-starter-doc-maven-plugin 88 | 89 | 90 | org.springframework.cloud.stream.app.plugin 91 | spring-cloud-stream-app-maven-plugin 92 | 93 | ${session.executionRootDirectory}/apps 94 | ${project.version} 95 | 96 | scs-bom 97 | org.springframework.cloud.task.app 98 | composedtaskrunner-task-app-dependencies 99 | ${project.version} 100 | 101 | 102 | 103 | org.springframework.cloud.task.app.composedtaskrunner.ComposedTaskRunnerConfiguration.class 104 | 105 | 106 | 107 | spring.cloud.task.closecontextEnabled=true 108 | 109 | true 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedBatchConfigurer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import javax.sql.DataSource; 20 | 21 | import org.springframework.boot.autoconfigure.batch.BasicBatchConfigurer; 22 | import org.springframework.boot.autoconfigure.batch.BatchProperties; 23 | import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; 24 | import org.springframework.transaction.annotation.Isolation; 25 | 26 | /** 27 | * A BatchConfigurer for CTR that will establish the transaction isolation lavel to READ_COMMITTED. 28 | * 29 | * @author Glenn Renfro 30 | */ 31 | public class ComposedBatchConfigurer extends BasicBatchConfigurer { 32 | /** 33 | * Create a new {@link BasicBatchConfigurer} instance. 34 | * 35 | * @param properties the batch properties 36 | * @param dataSource the underlying data source 37 | * @param transactionManagerCustomizers transaction manager customizers (or 38 | * {@code null}) 39 | */ 40 | protected ComposedBatchConfigurer(BatchProperties properties, DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers) { 41 | super(properties, dataSource, transactionManagerCustomizers); 42 | } 43 | 44 | @Override 45 | protected String determineIsolationLevel() { 46 | return "ISOLATION_" + Isolation.READ_COMMITTED; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedRunnerJobFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.Deque; 20 | import java.util.HashMap; 21 | import java.util.LinkedList; 22 | import java.util.Map; 23 | import java.util.UUID; 24 | 25 | import org.springframework.batch.core.Job; 26 | import org.springframework.batch.core.Step; 27 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 28 | import org.springframework.batch.core.job.builder.FlowBuilder; 29 | import org.springframework.batch.core.job.builder.FlowJobBuilder; 30 | import org.springframework.batch.core.job.flow.Flow; 31 | import org.springframework.batch.core.launch.support.RunIdIncrementer; 32 | import org.springframework.beans.factory.FactoryBean; 33 | import org.springframework.beans.factory.annotation.Autowired; 34 | import org.springframework.cloud.dataflow.core.dsl.FlowNode; 35 | import org.springframework.cloud.dataflow.core.dsl.LabelledTaskNode; 36 | import org.springframework.cloud.dataflow.core.dsl.SplitNode; 37 | import org.springframework.cloud.dataflow.core.dsl.TaskAppNode; 38 | import org.springframework.cloud.dataflow.core.dsl.TaskParser; 39 | import org.springframework.cloud.dataflow.core.dsl.TransitionNode; 40 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 41 | import org.springframework.cloud.task.repository.TaskNameResolver; 42 | import org.springframework.context.ApplicationContext; 43 | import org.springframework.core.task.TaskExecutor; 44 | import org.springframework.util.Assert; 45 | 46 | /** 47 | * Genererates a Composed Task Job Flow. 48 | * 49 | * @author Glenn Renfro 50 | * @author Ilayaperumal Gopinathan 51 | */ 52 | public class ComposedRunnerJobFactory implements FactoryBean { 53 | 54 | private static final String WILD_CARD = "*"; 55 | 56 | @Autowired 57 | private ApplicationContext context; 58 | 59 | @Autowired 60 | private TaskExecutor taskExecutor; 61 | 62 | @Autowired 63 | private JobBuilderFactory jobBuilderFactory; 64 | 65 | @Autowired 66 | private TaskNameResolver taskNameResolver; 67 | 68 | private final ComposedTaskProperties composedTaskProperties; 69 | 70 | private FlowBuilder flowBuilder; 71 | 72 | private Map taskBeanSuffixes = new HashMap<>(); 73 | 74 | private Deque jobDeque = new LinkedList<>(); 75 | 76 | private Deque visitorDeque; 77 | 78 | private Deque executionDeque = new LinkedList<>(); 79 | 80 | private String dsl; 81 | 82 | private boolean incrementInstanceEnabled; 83 | 84 | private int splitFlows = 1; 85 | 86 | private boolean hasNestedSplit = false; 87 | 88 | public ComposedRunnerJobFactory(ComposedTaskProperties properties) { 89 | this.composedTaskProperties = properties; 90 | Assert.notNull(properties.getGraph(), "The DSL must not be null"); 91 | this.dsl = properties.getGraph(); 92 | this.incrementInstanceEnabled = properties.isIncrementInstanceEnabled(); 93 | this.flowBuilder = new FlowBuilder<>(UUID.randomUUID().toString()); 94 | } 95 | 96 | @Override 97 | public Job getObject() throws Exception { 98 | ComposedRunnerVisitor composedRunnerVisitor = new ComposedRunnerVisitor(); 99 | 100 | TaskParser taskParser = new TaskParser("composed-task-runner", 101 | this.dsl,false,true); 102 | taskParser.parse().accept(composedRunnerVisitor); 103 | 104 | this.visitorDeque = composedRunnerVisitor.getFlow(); 105 | 106 | FlowJobBuilder builder = this.jobBuilderFactory 107 | .get(this.taskNameResolver.getTaskName()) 108 | .start(this.flowBuilder 109 | .start(createFlow()) 110 | .end()) 111 | .end(); 112 | if(this.incrementInstanceEnabled) { 113 | builder.incrementer(new RunIdIncrementer()); 114 | } 115 | return builder.build(); 116 | } 117 | 118 | @Override 119 | public Class getObjectType() { 120 | return Job.class; 121 | } 122 | 123 | @Override 124 | public boolean isSingleton() { 125 | return true; 126 | } 127 | 128 | private Flow createFlow() { 129 | 130 | while (!this.visitorDeque.isEmpty()) { 131 | 132 | if (this.visitorDeque.peek() instanceof TaskAppNode) { 133 | TaskAppNode taskAppNode = (TaskAppNode) this.visitorDeque.pop(); 134 | 135 | if (taskAppNode.hasTransitions()) { 136 | handleTransition(this.executionDeque, taskAppNode); 137 | } 138 | else { 139 | this.executionDeque.push(getTaskAppFlow(taskAppNode)); 140 | } 141 | } 142 | //When end marker of a split is found, process the split 143 | else if (this.visitorDeque.peek() instanceof SplitNode) { 144 | Deque splitNodeDeque = new LinkedList<>(); 145 | SplitNode splitNode = (SplitNode) this.visitorDeque.pop(); 146 | splitNodeDeque.push(splitNode); 147 | while (!this.visitorDeque.isEmpty() && !this.visitorDeque.peek().equals(splitNode)) { 148 | splitNodeDeque.push(this.visitorDeque.pop()); 149 | } 150 | splitNodeDeque.push(this.visitorDeque.pop()); 151 | handleSplit(splitNodeDeque, splitNode); 152 | } 153 | //When start marker of a DSL flow is found, process it. 154 | else if (this.visitorDeque.peek() instanceof FlowNode) { 155 | handleFlow(this.executionDeque); 156 | } 157 | } 158 | 159 | return this.jobDeque.pop(); 160 | } 161 | 162 | private void handleFlow(Deque executionDeque) { 163 | if(!executionDeque.isEmpty()) { 164 | this.flowBuilder.start(executionDeque.pop()); 165 | } 166 | 167 | while (!executionDeque.isEmpty()) { 168 | this.flowBuilder.next(executionDeque.pop()); 169 | } 170 | 171 | this.visitorDeque.pop(); 172 | this.jobDeque.push(this.flowBuilder.end()); 173 | } 174 | 175 | private void handleSplit(Deque visitorDeque, SplitNode splitNode) { 176 | this.executionDeque.push(processSplitNode(visitorDeque, splitNode)); 177 | } 178 | 179 | private Flow processSplitNode(Deque visitorDeque, SplitNode splitNode) { 180 | Deque flows = new LinkedList<>(); 181 | //For each node in the split process it as a DSL flow. 182 | for (LabelledTaskNode taskNode : splitNode.getSeries()) { 183 | Deque resultFlowDeque = new LinkedList<>(); 184 | flows.addAll(processSplitFlow(taskNode, resultFlowDeque)); 185 | } 186 | removeProcessedNodes(visitorDeque, splitNode); 187 | Flow nestedSplitFlow = new FlowBuilder.SplitBuilder<>( 188 | new FlowBuilder("Split" + UUID.randomUUID().toString()), 189 | taskExecutor) 190 | .add(flows.toArray(new Flow[flows.size()])) 191 | .build(); 192 | FlowBuilder taskAppFlowBuilder = 193 | new FlowBuilder<>("Flow" + UUID.randomUUID().toString()); 194 | if (this.hasNestedSplit) { 195 | this.splitFlows = flows.size(); 196 | int threadCorePoolSize = this.composedTaskProperties.getSplitThreadCorePoolSize(); 197 | Assert.isTrue(threadCorePoolSize >= this.splitFlows, 198 | "Split thread core pool size " + threadCorePoolSize + " should be equal or greater " 199 | + "than the depth of split flows " + (this.splitFlows +1) + "." 200 | + " Try setting the composed task property `splitThreadCorePoolSize`"); 201 | } 202 | return taskAppFlowBuilder.start(nestedSplitFlow).end(); 203 | } 204 | 205 | private void removeProcessedNodes(Deque visitorDeque, SplitNode splitNode) { 206 | //remove the nodes of the split since it has already been processed 207 | while (visitorDeque.peek() != null && !(visitorDeque.peek().equals(splitNode))) { 208 | visitorDeque.pop(); 209 | } 210 | // pop the SplitNode that marks the beginning of the split from the deque 211 | if (visitorDeque.peek() != null) { 212 | visitorDeque.pop(); 213 | } 214 | } 215 | 216 | /** 217 | * Processes each node in split as a DSL Flow. 218 | * @param node represents a single node in the split. 219 | * @return Deque of Job Flows that was obtained from the Node. 220 | */ 221 | private Deque processSplitFlow(LabelledTaskNode node, Deque resultFlowDeque) { 222 | TaskParser taskParser = new TaskParser("split_flow" + UUID.randomUUID().toString(), node.stringify(), 223 | false, true); 224 | ComposedRunnerVisitor splitElementVisitor = new ComposedRunnerVisitor(); 225 | taskParser.parse().accept(splitElementVisitor); 226 | 227 | Deque splitElementDeque = splitElementVisitor.getFlow(); 228 | Deque elementFlowDeque = new LinkedList<>(); 229 | 230 | while (!splitElementDeque.isEmpty()) { 231 | 232 | if (splitElementDeque.peek() instanceof TaskAppNode) { 233 | 234 | TaskAppNode taskAppNode = (TaskAppNode) splitElementDeque.pop(); 235 | 236 | if (taskAppNode.hasTransitions()) { 237 | handleTransition(elementFlowDeque, taskAppNode); 238 | } 239 | else { 240 | elementFlowDeque.push( 241 | getTaskAppFlow(taskAppNode)); 242 | } 243 | } 244 | else if (splitElementDeque.peek() instanceof FlowNode) { 245 | resultFlowDeque.push(handleFlowForSegment(elementFlowDeque)); 246 | splitElementDeque.pop(); 247 | } 248 | else if (splitElementDeque.peek() instanceof SplitNode) { 249 | this.hasNestedSplit = true; 250 | Deque splitNodeDeque = new LinkedList<>(); 251 | SplitNode splitNode = (SplitNode) splitElementDeque.pop(); 252 | splitNodeDeque.push(splitNode); 253 | while (!splitElementDeque.isEmpty() && !splitElementDeque.peek().equals(splitNode)) { 254 | splitNodeDeque.push(splitElementDeque.pop()); 255 | } 256 | splitNodeDeque.push(splitElementDeque.pop()); 257 | elementFlowDeque.push(processSplitNode(splitNodeDeque, splitNode)); 258 | } 259 | } 260 | return resultFlowDeque; 261 | } 262 | 263 | private Flow handleFlowForSegment(Deque resultFlowDeque) { 264 | FlowBuilder localTaskAppFlowBuilder = 265 | new FlowBuilder<>("Flow" + UUID.randomUUID().toString()); 266 | 267 | if(!resultFlowDeque.isEmpty()) { 268 | localTaskAppFlowBuilder.start(resultFlowDeque.pop()); 269 | 270 | } 271 | 272 | while (!resultFlowDeque.isEmpty()) { 273 | localTaskAppFlowBuilder.next(resultFlowDeque.pop()); 274 | } 275 | 276 | return localTaskAppFlowBuilder.end(); 277 | } 278 | 279 | private void handleTransition(Deque resultFlowDeque, 280 | TaskAppNode taskAppNode) { 281 | String beanName = getBeanName(taskAppNode); 282 | Step currentStep = this.context.getBean(beanName, Step.class); 283 | FlowBuilder builder = new FlowBuilder(beanName) 284 | .from(currentStep); 285 | 286 | boolean wildCardPresent = false; 287 | 288 | for (TransitionNode transitionNode : taskAppNode.getTransitions()) { 289 | String transitionBeanName = getBeanName(transitionNode); 290 | 291 | wildCardPresent = transitionNode.getStatusToCheck().equals(WILD_CARD); 292 | 293 | Step transitionStep = this.context.getBean(transitionBeanName, 294 | Step.class); 295 | builder.on(transitionNode.getStatusToCheck()).to(transitionStep) 296 | .from(currentStep); 297 | } 298 | 299 | if (wildCardPresent && !resultFlowDeque.isEmpty()) { 300 | throw new IllegalStateException( 301 | "Invalid flow following '*' specifier."); 302 | } 303 | else { 304 | //if there are nodes are in the execution Deque. Make sure that 305 | //they are processed as a target of the wildcard instead of the 306 | //whole transition. 307 | if (!resultFlowDeque.isEmpty()) { 308 | builder.on(WILD_CARD).to(handleFlowForSegment(resultFlowDeque)).from(currentStep); 309 | } 310 | } 311 | 312 | resultFlowDeque.push(builder.end()); 313 | } 314 | 315 | private String getBeanName(TransitionNode transition) { 316 | if (transition.getTargetLabel() != null) { 317 | return transition.getTargetLabel(); 318 | } 319 | 320 | return getBeanName(transition.getTargetApp()); 321 | } 322 | 323 | 324 | private String getBeanName(TaskAppNode taskApp) { 325 | if (taskApp.getLabel() != null) { 326 | return taskApp.getLabel().stringValue(); 327 | } 328 | 329 | String taskName = taskApp.getName(); 330 | 331 | if (taskName.contains("->")) { 332 | taskName = taskName.substring(taskName.indexOf("->") + 2); 333 | } 334 | 335 | return getBeanName(taskName); 336 | } 337 | 338 | private String getBeanName(String taskName) { 339 | int taskSuffix = 0; 340 | 341 | if (this.taskBeanSuffixes.containsKey(taskName)) { 342 | taskSuffix = this.taskBeanSuffixes.get(taskName); 343 | } 344 | 345 | String result = String.format("%s_%s", taskName, taskSuffix++); 346 | this.taskBeanSuffixes.put(taskName, taskSuffix); 347 | 348 | return result; 349 | } 350 | 351 | private Flow getTaskAppFlow(TaskAppNode taskApp) { 352 | String beanName = getBeanName(taskApp); 353 | Step currentStep = this.context.getBean(beanName, Step.class); 354 | 355 | return new FlowBuilder(beanName).from(currentStep).end(); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedRunnerVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.Deque; 20 | import java.util.LinkedList; 21 | 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | 25 | import org.springframework.cloud.dataflow.core.dsl.FlowNode; 26 | import org.springframework.cloud.dataflow.core.dsl.LabelledTaskNode; 27 | import org.springframework.cloud.dataflow.core.dsl.SplitNode; 28 | import org.springframework.cloud.dataflow.core.dsl.TaskAppNode; 29 | import org.springframework.cloud.dataflow.core.dsl.TaskVisitor; 30 | 31 | /** 32 | * Creates a stack of task executions from a composed task DSL. 33 | * 34 | * @author Glenn Renfro. 35 | */ 36 | public class ComposedRunnerVisitor extends TaskVisitor { 37 | 38 | private Deque flowDeque = new LinkedList<>(); 39 | 40 | private static final Log logger = LogFactory.getLog(ComposedRunnerVisitor.class); 41 | 42 | /** 43 | * Push the flow node on the stack to record the beginning of the flow. 44 | * 45 | * @param flow the flow which represents things to execute in sequence 46 | * @return false to skip visiting this flow 47 | */ 48 | public boolean preVisit(FlowNode flow) { 49 | logger.debug("Pre Visit Flow: " + flow); 50 | this.flowDeque.push(flow); 51 | return true; 52 | } 53 | 54 | /** 55 | * Push the split node on the stack to record the beginning of the split. 56 | * 57 | * @param split the information pertaining to the elements contained in the 58 | * split. 59 | */ 60 | public void visit(SplitNode split) { 61 | logger.debug("Visit Split: " + split); 62 | this.flowDeque.push(split); 63 | } 64 | 65 | /** 66 | * Push the split node on the stack to record the Ending of the split. 67 | * 68 | * @param split the information pertaining to the elements contained in the 69 | * split. 70 | */ 71 | public void postVisit(SplitNode split) { 72 | logger.debug("Post Visit Split: " + split); 73 | this.flowDeque.push(split); 74 | } 75 | 76 | /** 77 | * Push the task app node on the stack to record the task app. 78 | * 79 | * @param taskApp the information pertaining to the taskAppNode contained 80 | * in the flow. 81 | */ 82 | public void visit(TaskAppNode taskApp) { 83 | logger.debug("Visit taskApp: " + taskApp); 84 | this.flowDeque.push(taskApp); 85 | } 86 | 87 | public Deque getFlow() { 88 | return this.flowDeque; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | /** 23 | * Accepts a composed task DSL via the command line args and executes the 24 | * tasks based on the DSL. 25 | */ 26 | @SpringBootApplication 27 | public class ComposedTaskRunner { 28 | 29 | public static void main(String[] args) { 30 | SpringApplication.run(ComposedTaskRunner.class, args); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunnerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import javax.sql.DataSource; 20 | 21 | import org.springframework.batch.core.StepExecutionListener; 22 | import org.springframework.batch.core.configuration.annotation.BatchConfigurer; 23 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.boot.autoconfigure.batch.BatchProperties; 26 | import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; 27 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 28 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 29 | import org.springframework.cloud.task.configuration.EnableTask; 30 | import org.springframework.cloud.task.repository.TaskExplorer; 31 | import org.springframework.context.annotation.Bean; 32 | import org.springframework.context.annotation.Configuration; 33 | import org.springframework.context.annotation.Import; 34 | import org.springframework.core.task.TaskExecutor; 35 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 36 | 37 | /** 38 | * Configures the Job that will execute the Composed Task Execution. 39 | * 40 | * @author Glenn Renfro 41 | */ 42 | @EnableBatchProcessing 43 | @EnableTask 44 | @EnableConfigurationProperties(ComposedTaskProperties.class) 45 | @Configuration 46 | @Import(StepBeanDefinitionRegistrar.class) 47 | public class ComposedTaskRunnerConfiguration { 48 | 49 | @Autowired 50 | private ComposedTaskProperties properties; 51 | 52 | @Bean 53 | public StepExecutionListener composedTaskStepExecutionListener(TaskExplorer taskExplorer){ 54 | return new ComposedTaskStepExecutionListener(taskExplorer); 55 | } 56 | 57 | @Bean 58 | public ComposedRunnerJobFactory composedTaskJob() { 59 | 60 | return new ComposedRunnerJobFactory(this.properties); 61 | } 62 | 63 | @Bean 64 | public TaskExecutor taskExecutor() { 65 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 66 | taskExecutor.setCorePoolSize(properties.getSplitThreadCorePoolSize()); 67 | taskExecutor.setMaxPoolSize(properties.getSplitThreadMaxPoolSize()); 68 | taskExecutor.setKeepAliveSeconds(properties.getSplitThreadKeepAliveSeconds()); 69 | taskExecutor.setAllowCoreThreadTimeOut( 70 | properties.isSplitThreadAllowCoreThreadTimeout()); 71 | taskExecutor.setQueueCapacity(properties.getSplitThreadQueueCapacity()); 72 | taskExecutor.setWaitForTasksToCompleteOnShutdown( 73 | properties.isSplitThreadWaitForTasksToCompleteOnShutdown()); 74 | return taskExecutor; 75 | } 76 | 77 | @Bean 78 | public BatchConfigurer getComposedBatchConfigurer(BatchProperties properties, DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers) { 79 | return new ComposedBatchConfigurer(properties, dataSource, transactionManagerCustomizers); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunnerStepFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.UUID; 24 | 25 | import org.springframework.batch.core.Step; 26 | import org.springframework.batch.core.StepExecutionListener; 27 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 28 | import org.springframework.beans.factory.FactoryBean; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 31 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 32 | import org.springframework.cloud.task.configuration.TaskConfigurer; 33 | import org.springframework.cloud.task.configuration.TaskProperties; 34 | import org.springframework.transaction.annotation.Isolation; 35 | import org.springframework.transaction.interceptor.DefaultTransactionAttribute; 36 | import org.springframework.transaction.interceptor.TransactionAttribute; 37 | import org.springframework.util.Assert; 38 | 39 | /** 40 | * FactoryBean that creates a Spring Batch Step that executes a configured 41 | * TaskLaunchTasklet. 42 | * 43 | * @author Glenn Renfro 44 | * @author Michael Minella 45 | */ 46 | public class ComposedTaskRunnerStepFactory implements FactoryBean { 47 | 48 | private ComposedTaskProperties composedTaskProperties; 49 | 50 | private String taskName; 51 | 52 | private Map taskSpecificProps = new HashMap<>(); 53 | 54 | private List arguments = new ArrayList<>(); 55 | 56 | @Autowired 57 | private StepBuilderFactory steps; 58 | 59 | @Autowired 60 | private StepExecutionListener composedTaskStepExecutionListener; 61 | 62 | @Autowired 63 | private TaskOperations taskOperations; 64 | 65 | @Autowired 66 | private TaskConfigurer taskConfigurer; 67 | 68 | @Autowired 69 | private TaskProperties taskProperties; 70 | 71 | public ComposedTaskRunnerStepFactory( 72 | ComposedTaskProperties composedTaskProperties, String taskName) { 73 | Assert.notNull(composedTaskProperties, 74 | "composedTaskProperties must not be null"); 75 | Assert.hasText(taskName, "taskName must not be empty nor null"); 76 | 77 | this.composedTaskProperties = composedTaskProperties; 78 | this.taskName = taskName; 79 | } 80 | 81 | public void setTaskSpecificProps(Map taskSpecificProps) { 82 | if(taskSpecificProps != null) { 83 | this.taskSpecificProps = taskSpecificProps; 84 | } 85 | } 86 | 87 | public void setArguments(List arguments) { 88 | if(arguments != null) { 89 | this.arguments = arguments; 90 | } 91 | } 92 | 93 | @Override 94 | public Step getObject() throws Exception { 95 | TaskLauncherTasklet taskLauncherTasklet = new TaskLauncherTasklet( 96 | this.taskOperations, taskConfigurer.getTaskExplorer(), 97 | this.composedTaskProperties, this.taskName, taskProperties); 98 | 99 | taskLauncherTasklet.setArguments(this.arguments); 100 | taskLauncherTasklet.setProperties(this.taskSpecificProps); 101 | 102 | String stepName = this.taskName; 103 | 104 | return this.steps.get(stepName) 105 | .tasklet(taskLauncherTasklet) 106 | .transactionAttribute(getTransactionAttribute()) 107 | .listener(this.composedTaskStepExecutionListener) 108 | .build(); 109 | } 110 | 111 | /** 112 | * Using the default transaction attribute for the job will cause the 113 | * TaskLauncher not to see the latest state in the database but rather 114 | * what is in its transaction. By setting isolation to READ_COMMITTED 115 | * The task launcher can see latest state of the db. Since the changes 116 | * to the task execution are done by the tasks. 117 | 118 | * @return DefaultTransactionAttribute with isolation set to READ_COMMITTED. 119 | */ 120 | private TransactionAttribute getTransactionAttribute() { 121 | DefaultTransactionAttribute defaultTransactionAttribute = 122 | new DefaultTransactionAttribute(); 123 | defaultTransactionAttribute.setIsolationLevel( 124 | Isolation.READ_COMMITTED.value()); 125 | return defaultTransactionAttribute; 126 | } 127 | 128 | @Override 129 | public Class getObjectType() { 130 | return Step.class; 131 | } 132 | 133 | @Override 134 | public boolean isSingleton() { 135 | return true; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskStepExecutionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import org.apache.commons.logging.Log; 20 | import org.apache.commons.logging.LogFactory; 21 | 22 | import org.springframework.batch.core.ExitStatus; 23 | import org.springframework.batch.core.StepExecution; 24 | import org.springframework.batch.core.listener.StepExecutionListenerSupport; 25 | import org.springframework.cloud.task.repository.TaskExecution; 26 | import org.springframework.cloud.task.repository.TaskExplorer; 27 | import org.springframework.util.Assert; 28 | import org.springframework.util.StringUtils; 29 | 30 | /** 31 | * Listener for the TaskLauncherTasklet that waits for the task to complete 32 | * and sets the appropriate result for this step based on the launched task 33 | * exit code. 34 | * 35 | * @author Glenn Renfro 36 | */ 37 | public class ComposedTaskStepExecutionListener extends StepExecutionListenerSupport{ 38 | 39 | private TaskExplorer taskExplorer; 40 | 41 | private static final Log logger = LogFactory.getLog(ComposedTaskStepExecutionListener.class); 42 | 43 | public ComposedTaskStepExecutionListener(TaskExplorer taskExplorer) { 44 | Assert.notNull(taskExplorer, "taskExplorer must not be null."); 45 | this.taskExplorer = taskExplorer; 46 | } 47 | 48 | /** 49 | * If endTime for task is null then the ExitStatus will be set to UNKNOWN. 50 | * If an exitMessage is returned by the TaskExecution then the exit status 51 | * returned will be the ExitMessage. If no exitMessage is set for the task execution and the 52 | * task returns an exitCode ! = to zero an exit status of FAILED is 53 | * returned. If no exit message is set and the exit code of the task is 54 | * zero then the ExitStatus of COMPLETED is returned. 55 | * @param stepExecution The stepExecution that kicked of the Task. 56 | * @return ExitStatus of COMPLETED else FAILED. 57 | */ 58 | @Override 59 | public ExitStatus afterStep(StepExecution stepExecution) { 60 | ExitStatus result = ExitStatus.COMPLETED; 61 | logger.info(String.format("AfterStep processing for stepExecution %s", 62 | stepExecution.getStepName())); 63 | 64 | Long executionId = (Long) stepExecution.getExecutionContext().get("task-execution-id"); 65 | Assert.notNull(executionId, "TaskLauncherTasklet did not " + 66 | "return a task-execution-id. Check to see if task " + 67 | "exists."); 68 | 69 | TaskExecution resultExecution = this.taskExplorer.getTaskExecution(executionId); 70 | 71 | if (!StringUtils.isEmpty(resultExecution.getExitMessage())) { 72 | result = new ExitStatus(resultExecution.getExitMessage()); 73 | } 74 | else if (resultExecution.getExitCode() != 0) { 75 | result = ExitStatus.FAILED; 76 | } 77 | 78 | logger.info(String.format("AfterStep processing complete for " + 79 | "stepExecution %s with taskExecution %s", 80 | stepExecution.getStepName(), executionId)); 81 | return result; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/DataFlowConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import org.apache.commons.logging.Log; 20 | import org.apache.commons.logging.LogFactory; 21 | 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 24 | import org.springframework.cloud.common.security.support.OAuth2AccessTokenProvidingClientHttpRequestInterceptor; 25 | import org.springframework.cloud.dataflow.rest.client.DataFlowOperations; 26 | import org.springframework.cloud.dataflow.rest.client.DataFlowTemplate; 27 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 28 | import org.springframework.cloud.dataflow.rest.util.HttpClientConfigurer; 29 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 30 | import org.springframework.cloud.task.app.composedtaskrunner.support.OnOAuth2ClientCredentialsEnabled; 31 | import org.springframework.context.annotation.Bean; 32 | import org.springframework.context.annotation.Conditional; 33 | import org.springframework.context.annotation.Configuration; 34 | import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient; 35 | import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; 36 | import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; 37 | import org.springframework.security.oauth2.client.registration.ClientRegistration; 38 | import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; 39 | import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; 40 | import org.springframework.security.oauth2.core.AuthorizationGrantType; 41 | import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; 42 | import org.springframework.util.StringUtils; 43 | import org.springframework.web.client.RestTemplate; 44 | 45 | /** 46 | * Configures the beans required for Connectivity to the Data Flow Server. 47 | * 48 | * @author Glenn Renfro 49 | * @author Gunnar Hillert 50 | * @author Ilayaperumal Gopinathan 51 | */ 52 | @Configuration 53 | @EnableConfigurationProperties(ComposedTaskProperties.class) 54 | public class DataFlowConfiguration { 55 | private static Log logger = LogFactory.getLog(DataFlowConfiguration.class); 56 | 57 | @Autowired 58 | private ComposedTaskProperties properties; 59 | 60 | @Bean 61 | public TaskOperations taskOperations(DataFlowOperations dataFlowOperations) { 62 | return dataFlowOperations.taskOperations(); 63 | } 64 | 65 | /** 66 | * @param clientRegistrations Can be null. Only required for Client Credentials Grant authentication 67 | * @param clientCredentialsTokenResponseClient Can be null. Only required for Client Credentials Grant authentication 68 | * @return DataFlowOperations 69 | */ 70 | @Bean 71 | public DataFlowOperations dataFlowOperations( 72 | @Autowired(required = false) ClientRegistrationRepository clientRegistrations, 73 | @Autowired(required = false) OAuth2AccessTokenResponseClient clientCredentialsTokenResponseClient) { 74 | 75 | final RestTemplate restTemplate = DataFlowTemplate.getDefaultDataflowRestTemplate(); 76 | validateUsernamePassword(this.properties.getDataflowServerUsername(), this.properties.getDataflowServerPassword()); 77 | 78 | HttpClientConfigurer clientHttpRequestFactoryBuilder = null; 79 | 80 | if (this.properties.getOauth2ClientCredentialsClientId() != null 81 | || StringUtils.hasText(this.properties.getDataflowServerAccessToken()) 82 | || (StringUtils.hasText(this.properties.getDataflowServerUsername()) 83 | && StringUtils.hasText(this.properties.getDataflowServerPassword()))) { 84 | clientHttpRequestFactoryBuilder = HttpClientConfigurer.create(this.properties.getDataflowServerUri()); 85 | } 86 | 87 | String accessTokenValue = null; 88 | 89 | if (this.properties.getOauth2ClientCredentialsClientId() != null) { 90 | final ClientRegistration clientRegistration = clientRegistrations.findByRegistrationId("default"); 91 | final OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration); 92 | final OAuth2AccessTokenResponse res = clientCredentialsTokenResponseClient.getTokenResponse(grantRequest); 93 | accessTokenValue = res.getAccessToken().getTokenValue(); 94 | logger.debug("Configured OAuth2 Client Credentials for accessing the Data Flow Server"); 95 | } 96 | else if (StringUtils.hasText(this.properties.getDataflowServerAccessToken())) { 97 | accessTokenValue = this.properties.getDataflowServerAccessToken(); 98 | logger.debug("Configured OAuth2 Access Token for accessing the Data Flow Server"); 99 | } 100 | else if (StringUtils.hasText(this.properties.getDataflowServerUsername()) 101 | && StringUtils.hasText(this.properties.getDataflowServerPassword())) { 102 | accessTokenValue = null; 103 | clientHttpRequestFactoryBuilder.basicAuthCredentials(properties.getDataflowServerUsername(), properties.getDataflowServerPassword()); 104 | logger.debug("Configured basic security for accessing the Data Flow Server"); 105 | } 106 | else { 107 | logger.debug("Not configuring basic security for accessing the Data Flow Server"); 108 | } 109 | 110 | if (accessTokenValue != null) { 111 | restTemplate.getInterceptors().add(new OAuth2AccessTokenProvidingClientHttpRequestInterceptor(accessTokenValue)); 112 | } 113 | 114 | if (clientHttpRequestFactoryBuilder != null) { 115 | restTemplate.setRequestFactory(clientHttpRequestFactoryBuilder.buildClientHttpRequestFactory()); 116 | } 117 | 118 | return new DataFlowTemplate(this.properties.getDataflowServerUri(), restTemplate); 119 | } 120 | 121 | private void validateUsernamePassword(String userName, String password) { 122 | if (!StringUtils.isEmpty(password) && StringUtils.isEmpty(userName)) { 123 | throw new IllegalArgumentException("A password may be specified only together with a username"); 124 | } 125 | if (StringUtils.isEmpty(password) && !StringUtils.isEmpty(userName)) { 126 | throw new IllegalArgumentException("A username may be specified only together with a password"); 127 | } 128 | } 129 | 130 | @Configuration 131 | @Conditional(OnOAuth2ClientCredentialsEnabled.class) 132 | static class clientCredentialsConfiguration { 133 | @Bean 134 | public InMemoryClientRegistrationRepository clientRegistrationRepository( 135 | ComposedTaskProperties properties) { 136 | final ClientRegistration clientRegistration = ClientRegistration 137 | .withRegistrationId("default") 138 | .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) 139 | .tokenUri(properties.getOauth2ClientCredentialsTokenUri()) 140 | .clientId(properties.getOauth2ClientCredentialsClientId()) 141 | .clientSecret(properties.getOauth2ClientCredentialsClientSecret()) 142 | .scope(properties.getOauth2ClientCredentialsScopes()) 143 | .build(); 144 | return new InMemoryClientRegistrationRepository(clientRegistration); 145 | } 146 | 147 | @Bean 148 | OAuth2AccessTokenResponseClient clientCredentialsTokenResponseClient() { 149 | return new DefaultClientCredentialsTokenResponseClient(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/StepBeanDefinitionRegistrar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.net.URI; 20 | import java.net.URISyntaxException; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 25 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 26 | import org.springframework.cloud.dataflow.core.dsl.TaskAppNode; 27 | import org.springframework.cloud.dataflow.core.dsl.TaskParser; 28 | import org.springframework.cloud.dataflow.core.dsl.TaskVisitor; 29 | import org.springframework.cloud.dataflow.core.dsl.TransitionNode; 30 | import org.springframework.cloud.dataflow.rest.util.DeploymentPropertiesUtils; 31 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 32 | import org.springframework.context.EnvironmentAware; 33 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 34 | import org.springframework.core.env.Environment; 35 | import org.springframework.core.type.AnnotationMetadata; 36 | 37 | /** 38 | * Creates the Steps necessary to execute the directed graph of a Composed 39 | * Task. 40 | * 41 | * @author Michael Minella 42 | * @author Glenn Renfro 43 | * @author Ilayaperumal Gopinathan 44 | */ 45 | public class StepBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, 46 | EnvironmentAware { 47 | 48 | private Environment env; 49 | 50 | @Override 51 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 52 | BeanDefinitionRegistry registry) { 53 | ComposedTaskProperties properties = composedTaskProperties(); 54 | TaskParser taskParser = new TaskParser("bean-registration", 55 | properties.getGraph(), false, true); 56 | Map taskSuffixMap = getTaskApps(taskParser); 57 | for (String taskName : taskSuffixMap.keySet()) { 58 | //handles the possibility that multiple instances of 59 | // task definition exist in a composed task 60 | for (int taskSuffix = 0; taskSuffixMap.get(taskName) >= taskSuffix; taskSuffix++) { 61 | BeanDefinitionBuilder builder = BeanDefinitionBuilder 62 | .rootBeanDefinition(ComposedTaskRunnerStepFactory.class); 63 | builder.addConstructorArgValue(properties); 64 | builder.addConstructorArgValue(String.format("%s_%s", 65 | taskName, taskSuffix)); 66 | builder.addPropertyValue("taskSpecificProps", 67 | getPropertiesForTask(taskName, properties)); 68 | builder.addPropertyValue("arguments", properties.getComposedTaskArguments()); 69 | 70 | registry.registerBeanDefinition(String.format("%s_%s", 71 | taskName, taskSuffix), builder.getBeanDefinition()); 72 | } 73 | } 74 | } 75 | 76 | private Map getPropertiesForTask(String taskName, ComposedTaskProperties properties) { 77 | Map taskDeploymentProperties = 78 | DeploymentPropertiesUtils.parse(properties.getComposedTaskProperties()); 79 | Map deploymentProperties = new HashMap<>(); 80 | updateDeploymentProperties(String.format("app.%s.", taskName), taskDeploymentProperties, deploymentProperties); 81 | updateDeploymentProperties(String.format("deployer.%s.", taskName), taskDeploymentProperties, deploymentProperties); 82 | return deploymentProperties; 83 | } 84 | 85 | private void updateDeploymentProperties(String prefix, Map taskDeploymentProperties, 86 | Map deploymentProperties) { 87 | for (Map.Entry entry : taskDeploymentProperties.entrySet()) { 88 | if (entry.getKey().startsWith(prefix)) { 89 | deploymentProperties.put(entry.getKey() 90 | .substring(prefix.length()), entry.getValue()); 91 | } 92 | } 93 | } 94 | 95 | @Override 96 | public void setEnvironment(Environment environment) { 97 | this.env = environment; 98 | } 99 | 100 | 101 | private ComposedTaskProperties composedTaskProperties() { 102 | ComposedTaskProperties properties = new ComposedTaskProperties(); 103 | String dataFlowUriString = this.env.getProperty("dataflow-server-uri"); 104 | String maxWaitTime = this.env.getProperty("max-wait-time"); 105 | String intervalTimeBetweenChecks = 106 | this.env.getProperty("interval-time-between-checks"); 107 | properties.setGraph(this.env.getProperty("graph")); 108 | properties.setComposedTaskArguments( 109 | this.env.getProperty("composed-task-arguments")); 110 | properties.setComposedTaskProperties(this.env.getProperty("composed-task-properties")); 111 | 112 | if (maxWaitTime != null) { 113 | properties.setMaxWaitTime(Integer.valueOf(maxWaitTime)); 114 | } 115 | if (intervalTimeBetweenChecks != null) { 116 | properties.setIntervalTimeBetweenChecks(Integer.valueOf( 117 | intervalTimeBetweenChecks)); 118 | } 119 | if (dataFlowUriString != null) { 120 | try { 121 | properties.setDataflowServerUri(new URI(dataFlowUriString)); 122 | } 123 | catch (URISyntaxException e) { 124 | throw new IllegalArgumentException("Invalid Data Flow URI"); 125 | } 126 | } 127 | return properties; 128 | } 129 | 130 | /** 131 | * @return a {@link Map} of task app name as the key and the number of times it occurs 132 | * as the value. 133 | */ 134 | private Map getTaskApps(TaskParser taskParser) { 135 | TaskAppsMapCollector collector = new TaskAppsMapCollector(); 136 | taskParser.parse().accept(collector); 137 | return collector.getTaskApps(); 138 | } 139 | 140 | /** 141 | * Simple visitor that discovers all the tasks in use in the composed 142 | * task definition. 143 | */ 144 | static class TaskAppsMapCollector extends TaskVisitor { 145 | 146 | Map taskApps = new HashMap<>(); 147 | 148 | @Override 149 | public void visit(TaskAppNode taskApp) { 150 | if (taskApps.containsKey(taskApp.getName())) { 151 | Integer updatedCount = taskApps.get(taskApp.getName()) + 1; 152 | taskApps.put(taskApp.getName(), updatedCount); 153 | } 154 | else { 155 | taskApps.put(taskApp.getName(), 0); 156 | } 157 | } 158 | 159 | @Override 160 | public void visit(TransitionNode transition) { 161 | if (transition.isTargetApp()) { 162 | if (taskApps.containsKey(transition.getTargetApp())) { 163 | Integer updatedCount = taskApps.get(transition.getTargetApp()) + 1; 164 | taskApps.put(transition.getTargetApp().getName(), updatedCount); 165 | } 166 | else { 167 | taskApps.put(transition.getTargetApp().getName(), 0); 168 | } 169 | } 170 | } 171 | 172 | public Map getTaskApps() { 173 | return taskApps; 174 | } 175 | 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/TaskLauncherTasklet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | import org.apache.commons.logging.Log; 25 | import org.apache.commons.logging.LogFactory; 26 | 27 | import org.springframework.batch.core.StepContribution; 28 | import org.springframework.batch.core.UnexpectedJobExecutionException; 29 | import org.springframework.batch.core.scope.context.ChunkContext; 30 | import org.springframework.batch.core.step.tasklet.Tasklet; 31 | import org.springframework.batch.item.ExecutionContext; 32 | import org.springframework.batch.repeat.RepeatStatus; 33 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 34 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 35 | import org.springframework.cloud.task.app.composedtaskrunner.support.TaskExecutionTimeoutException; 36 | import org.springframework.cloud.task.configuration.TaskProperties; 37 | import org.springframework.cloud.task.repository.TaskExecution; 38 | import org.springframework.cloud.task.repository.TaskExplorer; 39 | import org.springframework.util.Assert; 40 | 41 | /** 42 | * Executes task launch request using Spring Cloud Data Flow's Restful API 43 | * then returns the execution id once the task launched. 44 | * 45 | * Note: This class is not thread-safe and as such should not be used as a singleton. 46 | * 47 | * @author Glenn Renfro 48 | */ 49 | public class TaskLauncherTasklet implements Tasklet { 50 | 51 | private ComposedTaskProperties composedTaskProperties; 52 | 53 | private TaskExplorer taskExplorer; 54 | 55 | private TaskOperations taskOperations; 56 | 57 | private Map properties; 58 | 59 | private List arguments; 60 | 61 | private String taskName; 62 | 63 | private static final Log logger = LogFactory.getLog(TaskLauncherTasklet.class); 64 | 65 | private Long executionId; 66 | 67 | private long timeout; 68 | 69 | TaskProperties taskProperties; 70 | 71 | public TaskLauncherTasklet( 72 | TaskOperations taskOperations, TaskExplorer taskExplorer, 73 | ComposedTaskProperties composedTaskProperties, String taskName, 74 | TaskProperties taskProperties) { 75 | Assert.hasText(taskName, "taskName must not be empty nor null."); 76 | Assert.notNull(taskOperations, "taskOperations must not be null."); 77 | Assert.notNull(taskExplorer, "taskExplorer must not be null."); 78 | Assert.notNull(composedTaskProperties, 79 | "composedTaskProperties must not be null"); 80 | 81 | this.taskName = taskName; 82 | this.taskOperations = taskOperations; 83 | this.taskExplorer = taskExplorer; 84 | this.composedTaskProperties = composedTaskProperties; 85 | this.taskProperties = taskProperties; 86 | } 87 | 88 | public void setProperties(Map properties) { 89 | if(properties != null) { 90 | this.properties = properties; 91 | } 92 | else { 93 | this.properties = new HashMap<>(0); 94 | } 95 | } 96 | 97 | public void setArguments(List arguments) { 98 | if(arguments != null) { 99 | this.arguments = arguments; 100 | } 101 | else { 102 | this.arguments = new ArrayList<>(0); 103 | } 104 | } 105 | 106 | /** 107 | * Executes the task as specified by the taskName with the associated 108 | * properties and arguments. 109 | * 110 | * @param contribution mutable state to be passed back to update the current step execution 111 | * @param chunkContext contains the task-execution-id used by the listener. 112 | * @return Repeat status of FINISHED. 113 | */ 114 | @Override 115 | public RepeatStatus execute(StepContribution contribution, 116 | ChunkContext chunkContext) { 117 | if (this.executionId == null) { 118 | this.timeout = System.currentTimeMillis() + 119 | this.composedTaskProperties.getMaxWaitTime(); 120 | logger.debug("Wait time for this task to complete is " + 121 | this.composedTaskProperties.getMaxWaitTime()); 122 | logger.debug("Interval check time for this task to complete is " + 123 | this.composedTaskProperties.getIntervalTimeBetweenChecks()); 124 | 125 | String tmpTaskName = this.taskName.substring(0, 126 | this.taskName.lastIndexOf('_')); 127 | 128 | List args = this.arguments; 129 | 130 | ExecutionContext stepExecutionContext = chunkContext.getStepContext().getStepExecution(). 131 | getExecutionContext(); 132 | if (stepExecutionContext.containsKey("task-arguments")) { 133 | args = (List) stepExecutionContext.get("task-arguments"); 134 | } 135 | if(this.taskProperties.getExecutionid() != null) { 136 | args.add("--spring.cloud.task.parent-execution-id=" + this.taskProperties.getExecutionid()); 137 | } 138 | this.executionId = this.taskOperations.launch(tmpTaskName, 139 | this.properties, args, null); 140 | 141 | stepExecutionContext.put("task-execution-id", executionId); 142 | stepExecutionContext.put("task-arguments", args); 143 | } 144 | else { 145 | try { 146 | Thread.sleep(this.composedTaskProperties.getIntervalTimeBetweenChecks()); 147 | } 148 | catch (InterruptedException e) { 149 | Thread.currentThread().interrupt(); 150 | throw new IllegalStateException(e.getMessage(), e); 151 | } 152 | 153 | TaskExecution taskExecution = 154 | this.taskExplorer.getTaskExecution(this.executionId); 155 | if (taskExecution != null && taskExecution.getEndTime() != null) { 156 | if (taskExecution.getExitCode() == null) { 157 | throw new UnexpectedJobExecutionException("Task returned a null exit code."); 158 | } 159 | else if (taskExecution.getExitCode() != 0) { 160 | throw new UnexpectedJobExecutionException("Task returned a non zero exit code."); 161 | } 162 | else { 163 | return RepeatStatus.FINISHED; 164 | } 165 | } 166 | if (this.composedTaskProperties.getMaxWaitTime() > 0 && 167 | System.currentTimeMillis() > timeout) { 168 | throw new TaskExecutionTimeoutException(String.format( 169 | "Timeout occurred while processing task with Execution Id %s", 170 | this.executionId)); 171 | } 172 | } 173 | return RepeatStatus.CONTINUABLE; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/properties/ComposedTaskProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner.properties; 18 | 19 | import java.net.URI; 20 | import java.net.URISyntaxException; 21 | import java.util.Set; 22 | 23 | import org.springframework.boot.context.properties.ConfigurationProperties; 24 | 25 | /** 26 | * Configuration properties used to setup the ComposedTaskRunner. 27 | * 28 | * @author Glenn Renfro 29 | * @author Gunnar Hillert 30 | */ 31 | @ConfigurationProperties 32 | public class ComposedTaskProperties { 33 | 34 | public static final int MAX_WAIT_TIME_DEFAULT = 0; 35 | 36 | public static final int INTERVAL_TIME_BETWEEN_CHECKS_DEFAULT = 10000; 37 | 38 | public static final int SPLIT_THREAD_CORE_POOL_SIZE_DEFAULT = 4; 39 | 40 | public static final int SPLIT_THREAD_KEEP_ALIVE_SECONDS_DEFAULT = 60; 41 | 42 | public static final int SPLIT_THREAD_MAX_POOL_SIZE_DEFAULT = Integer.MAX_VALUE; 43 | 44 | public static final int SPLIT_THREAD_QUEUE_CAPACITY_DEFAULT = Integer.MAX_VALUE; 45 | 46 | /** 47 | * The maximum amount of time in millis that a individual step can run before 48 | * the execution of the Composed task is failed. 49 | */ 50 | private int maxWaitTime = MAX_WAIT_TIME_DEFAULT; 51 | 52 | /** 53 | * The amount of time in millis that the ComposedTaskRunner 54 | * will wait between checks of the database to see if a task has completed. 55 | */ 56 | private int intervalTimeBetweenChecks = INTERVAL_TIME_BETWEEN_CHECKS_DEFAULT; 57 | 58 | /** 59 | * The URI for the dataflow server that will receive task launch requests. 60 | * Default is http://localhost:9393; 61 | */ 62 | private URI dataflowServerUri; 63 | 64 | /** 65 | * The optional username for the dataflow server that will receive task launch requests. 66 | * Used to access the the dataflow server using Basic Authentication. Not used if {@link #dataflowServerAccessToken} is set. 67 | */ 68 | private String dataflowServerUsername; 69 | 70 | /** 71 | * The optional password for the dataflow server that will receive task launch requests. 72 | * Used to access the the dataflow server using Basic Authentication. Not used if {@link #dataflowServerAccessToken} is set. 73 | */ 74 | private String dataflowServerPassword; 75 | 76 | /** 77 | * The optional OAuth2 Access Token. 78 | */ 79 | private String dataflowServerAccessToken; 80 | 81 | /** 82 | * The OAuth2 Client Id (Used for the client credentials grant). 83 | * 84 | * If not null, then the following properties are ignored: 85 | * 86 | *
        87 | *
      • dataflowServerUsername 88 | *
      • dataflowServerPassword 89 | *
      • dataflowServerAccessToken 90 | *
          91 | */ 92 | private String oauth2ClientCredentialsClientId; 93 | 94 | /** 95 | * The OAuth2 Client Secret (Used for the client credentials grant). 96 | */ 97 | private String oauth2ClientCredentialsClientSecret; 98 | 99 | /** 100 | * Token URI for the OAuth2 provider (Used for the client credentials grant). 101 | */ 102 | private String oauth2ClientCredentialsTokenUri; 103 | 104 | /** 105 | * OAuth2 Authorization scopes (Used for the client credentials grant). 106 | */ 107 | private Set oauth2ClientCredentialsScopes; 108 | 109 | /** 110 | * The DSL for the composed task directed graph. 111 | */ 112 | private String graph; 113 | 114 | /** 115 | * The properties to be used for each of the tasks as well as their deployments. 116 | */ 117 | private String composedTaskProperties; 118 | 119 | /** 120 | * The arguments to be used for each of the tasks. 121 | */ 122 | private String composedTaskArguments; 123 | 124 | /** 125 | * Specifies whether to allow split core threads to timeout. 126 | * Default is false; 127 | */ 128 | private boolean splitThreadAllowCoreThreadTimeout; 129 | 130 | /** 131 | * Split's core pool size. 132 | * Default is 4; 133 | */ 134 | private int splitThreadCorePoolSize = SPLIT_THREAD_CORE_POOL_SIZE_DEFAULT; 135 | 136 | /** 137 | * Split's thread keep alive seconds. 138 | * Default is 60. 139 | */ 140 | private int splitThreadKeepAliveSeconds = SPLIT_THREAD_KEEP_ALIVE_SECONDS_DEFAULT; 141 | 142 | /** 143 | * Split's maximum pool size. 144 | * Default is {@code Integer.MAX_VALUE}. 145 | */ 146 | private int splitThreadMaxPoolSize = SPLIT_THREAD_MAX_POOL_SIZE_DEFAULT; 147 | 148 | /** 149 | * Capacity for Split's BlockingQueue. 150 | * Default is {@code Integer.MAX_VALUE}. 151 | */ 152 | private int splitThreadQueueCapacity = SPLIT_THREAD_QUEUE_CAPACITY_DEFAULT; 153 | 154 | /** 155 | * Whether to wait for scheduled tasks to complete on shutdown, not 156 | * interrupting running tasks and executing all tasks in the queue. 157 | * Default is false; 158 | */ 159 | private boolean splitThreadWaitForTasksToCompleteOnShutdown; 160 | 161 | /** 162 | * Allows a single ComposedTaskRunner instance to be re-executed without 163 | * changing the parameters. Default is false which means a 164 | * ComposedTaskRunner instance can only be executed once with a given set 165 | * of parameters, if true it can be re-executed. 166 | */ 167 | private boolean incrementInstanceEnabled = false; 168 | 169 | public ComposedTaskProperties() { 170 | try { 171 | this.dataflowServerUri = new URI("http://localhost:9393"); 172 | } 173 | catch (URISyntaxException e) { 174 | throw new IllegalStateException("Invalid Spring Cloud Data Flow Server URI", e); 175 | } 176 | } 177 | 178 | public int getMaxWaitTime() { 179 | return this.maxWaitTime; 180 | } 181 | 182 | public void setMaxWaitTime(int maxWaitTime) { 183 | this.maxWaitTime = maxWaitTime; 184 | } 185 | 186 | public int getIntervalTimeBetweenChecks() { 187 | return intervalTimeBetweenChecks; 188 | } 189 | 190 | public void setIntervalTimeBetweenChecks(int intervalTimeBetweenChecks) { 191 | this.intervalTimeBetweenChecks = intervalTimeBetweenChecks; 192 | } 193 | 194 | public URI getDataflowServerUri() { 195 | return dataflowServerUri; 196 | } 197 | 198 | public void setDataflowServerUri(URI dataflowServerUri) { 199 | this.dataflowServerUri = dataflowServerUri; 200 | } 201 | 202 | public String getDataflowServerUsername() { 203 | return dataflowServerUsername; 204 | } 205 | 206 | public void setDataflowServerUsername(String dataflowServerUsername) { 207 | this.dataflowServerUsername = dataflowServerUsername; 208 | } 209 | 210 | public String getDataflowServerPassword() { 211 | return dataflowServerPassword; 212 | } 213 | 214 | public void setDataflowServerPassword(String dataflowServerPassword) { 215 | this.dataflowServerPassword = dataflowServerPassword; 216 | } 217 | 218 | public String getGraph() { 219 | return this.graph; 220 | } 221 | 222 | public void setGraph(String graph) { 223 | this.graph = graph; 224 | } 225 | 226 | public String getComposedTaskProperties() { 227 | return this.composedTaskProperties; 228 | } 229 | 230 | public void setComposedTaskProperties(String composedTaskProperties) { 231 | this.composedTaskProperties = composedTaskProperties; 232 | } 233 | 234 | public String getComposedTaskArguments() { 235 | return this.composedTaskArguments; 236 | } 237 | 238 | public void setComposedTaskArguments(String composedTaskArguments) { 239 | this.composedTaskArguments = composedTaskArguments; 240 | } 241 | 242 | public boolean isSplitThreadAllowCoreThreadTimeout() { 243 | return splitThreadAllowCoreThreadTimeout; 244 | } 245 | 246 | public void setSplitThreadAllowCoreThreadTimeout(boolean splitThreadAllowCoreThreadTimeout) { 247 | this.splitThreadAllowCoreThreadTimeout = splitThreadAllowCoreThreadTimeout; 248 | } 249 | 250 | public int getSplitThreadCorePoolSize() { 251 | return splitThreadCorePoolSize; 252 | } 253 | 254 | public void setSplitThreadCorePoolSize(int splitThreadCorePoolSize) { 255 | this.splitThreadCorePoolSize = splitThreadCorePoolSize; 256 | } 257 | 258 | public int getSplitThreadKeepAliveSeconds() { 259 | return splitThreadKeepAliveSeconds; 260 | } 261 | 262 | public void setSplitThreadKeepAliveSeconds(int splitThreadKeepAliveSeconds) { 263 | this.splitThreadKeepAliveSeconds = splitThreadKeepAliveSeconds; 264 | } 265 | 266 | public int getSplitThreadMaxPoolSize() { 267 | return splitThreadMaxPoolSize; 268 | } 269 | 270 | public void setSplitThreadMaxPoolSize(int splitThreadMaxPoolSize) { 271 | this.splitThreadMaxPoolSize = splitThreadMaxPoolSize; 272 | } 273 | 274 | public int getSplitThreadQueueCapacity() { 275 | return splitThreadQueueCapacity; 276 | } 277 | 278 | public void setSplitThreadQueueCapacity(int splitThreadQueueCapacity) { 279 | this.splitThreadQueueCapacity = splitThreadQueueCapacity; 280 | } 281 | 282 | public boolean isSplitThreadWaitForTasksToCompleteOnShutdown() { 283 | return splitThreadWaitForTasksToCompleteOnShutdown; 284 | } 285 | 286 | public void setSplitThreadWaitForTasksToCompleteOnShutdown(boolean splitThreadWaitForTasksToCompleteOnShutdown) { 287 | this.splitThreadWaitForTasksToCompleteOnShutdown = splitThreadWaitForTasksToCompleteOnShutdown; 288 | } 289 | 290 | public boolean isIncrementInstanceEnabled() { 291 | return incrementInstanceEnabled; 292 | } 293 | 294 | public void setIncrementInstanceEnabled(boolean incrementInstanceEnabled) { 295 | this.incrementInstanceEnabled = incrementInstanceEnabled; 296 | } 297 | 298 | public String getDataflowServerAccessToken() { 299 | return dataflowServerAccessToken; 300 | } 301 | 302 | public void setDataflowServerAccessToken(String dataflowServerAccessToken) { 303 | this.dataflowServerAccessToken = dataflowServerAccessToken; 304 | } 305 | 306 | public String getOauth2ClientCredentialsClientId() { 307 | return oauth2ClientCredentialsClientId; 308 | } 309 | 310 | public void setOauth2ClientCredentialsClientId(String oauth2ClientCredentialsClientId) { 311 | this.oauth2ClientCredentialsClientId = oauth2ClientCredentialsClientId; 312 | } 313 | 314 | public String getOauth2ClientCredentialsClientSecret() { 315 | return oauth2ClientCredentialsClientSecret; 316 | } 317 | 318 | public void setOauth2ClientCredentialsClientSecret(String oauth2ClientCredentialsClientSecret) { 319 | this.oauth2ClientCredentialsClientSecret = oauth2ClientCredentialsClientSecret; 320 | } 321 | 322 | public String getOauth2ClientCredentialsTokenUri() { 323 | return oauth2ClientCredentialsTokenUri; 324 | } 325 | 326 | public void setOauth2ClientCredentialsTokenUri(String oauth2ClientCredentialsTokenUri) { 327 | this.oauth2ClientCredentialsTokenUri = oauth2ClientCredentialsTokenUri; 328 | } 329 | 330 | public Set getOauth2ClientCredentialsScopes() { 331 | return oauth2ClientCredentialsScopes; 332 | } 333 | 334 | public void setOauth2ClientCredentialsScopes(Set oauth2ClientCredentialsScopes) { 335 | this.oauth2ClientCredentialsScopes = oauth2ClientCredentialsScopes; 336 | } 337 | 338 | } 339 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/support/OnOAuth2ClientCredentialsEnabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.task.app.composedtaskrunner.support; 17 | 18 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 19 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 20 | import org.springframework.context.annotation.Condition; 21 | import org.springframework.context.annotation.ConditionContext; 22 | import org.springframework.core.type.AnnotatedTypeMetadata; 23 | import org.springframework.util.StringUtils; 24 | 25 | /** 26 | * {@link Condition} that is only valid if the property 27 | * {@code oauth2-client-credentials} exists. 28 | * 29 | * @author Gunnar Hillert 30 | */ 31 | public class OnOAuth2ClientCredentialsEnabled extends SpringBootCondition { 32 | 33 | @Override 34 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 35 | String propertyValue = context.getEnvironment().getProperty("oauth2-client-credentials-client-id"); 36 | return new ConditionOutcome(StringUtils.hasText(propertyValue), "OAuth2 Enabled"); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/java/org/springframework/cloud/task/app/composedtaskrunner/support/TaskExecutionTimeoutException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.task.app.composedtaskrunner.support; 17 | 18 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 19 | 20 | /** 21 | * Exception thrown when a task execution exceeds the configured 22 | * {@link ComposedTaskProperties#getMaxWaitTime()}. 23 | * 24 | * @author Glenn Renfro 25 | */ 26 | public class TaskExecutionTimeoutException extends RuntimeException { 27 | 28 | public TaskExecutionTimeoutException(String message) { 29 | super(message); 30 | } 31 | 32 | public TaskExecutionTimeoutException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | configuration-properties.classes=org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties 18 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/resources/META-INF/spring-configuration-metadata-whitelist.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | configuration-properties.classes=org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties 18 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _____ _ _______ _ 2 | / ____| | | |__ __| | | 3 | | | ___ _ __ ___ _ __ ___ ___ ___ __| | | | __ _ ___| | __ 4 | | | / _ \| '_ ` _ \| '_ \ / _ \/ __|/ _ \/ _` | | |/ _` / __| |/ / 5 | | |___| (_) | | | | | | |_) | (_) \__ \ __/ (_| | | | (_| \__ \ < 6 | \_____\___/|_| |_| |_| .__/ \___/|___/\___|\__,_| |_|\__,_|___/_|\_\ 7 | | | 8 | |_| 9 | _____ 10 | | __ \ 11 | | |__) | _ _ __ _ __ ___ _ __ 12 | | _ / | | | '_ \| '_ \ / _ \ '__| 13 | | | \ \ |_| | | | | | | | __/ | 14 | |_| \_\__,_|_| |_|_| |_|\___|_| 15 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedRunnerVisitorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.Collection; 23 | import java.util.Comparator; 24 | import java.util.HashSet; 25 | import java.util.Iterator; 26 | import java.util.List; 27 | import java.util.Set; 28 | 29 | import org.junit.After; 30 | import org.junit.Ignore; 31 | import org.junit.Test; 32 | 33 | import org.springframework.batch.core.JobExecution; 34 | import org.springframework.batch.core.JobInstance; 35 | import org.springframework.batch.core.StepExecution; 36 | import org.springframework.batch.core.explore.JobExplorer; 37 | import org.springframework.beans.factory.BeanCreationException; 38 | import org.springframework.boot.SpringApplication; 39 | import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; 40 | import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; 41 | import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; 42 | import org.springframework.cloud.task.app.composedtaskrunner.configuration.ComposedRunnerVisitorConfiguration; 43 | import org.springframework.cloud.task.batch.configuration.TaskBatchAutoConfiguration; 44 | import org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration; 45 | import org.springframework.context.ConfigurableApplicationContext; 46 | 47 | import static junit.framework.TestCase.assertTrue; 48 | import static org.assertj.core.api.Assertions.assertThat; 49 | import static org.junit.Assert.assertEquals; 50 | import static org.junit.jupiter.api.Assertions.assertThrows; 51 | 52 | /** 53 | * @author Glenn Renfro 54 | */ 55 | public class ComposedRunnerVisitorTests { 56 | 57 | private static final String CLOSE_CONTEXT_ARG = "--spring.cloud.task.closecontext_enable=false"; 58 | private static final String TASK_NAME_ARG = "--spring.cloud.task.name=job"; 59 | private static final String INVALID_FLOW_MSG = "Invalid flow following '*' specifier."; 60 | 61 | private ConfigurableApplicationContext applicationContext; 62 | 63 | @After 64 | public void tearDown() { 65 | if (this.applicationContext != null) { 66 | this.applicationContext.close(); 67 | } 68 | } 69 | 70 | @Test 71 | public void singleTest() { 72 | setupContextForGraph("AAA"); 73 | Collection stepExecutions = getStepExecutions(); 74 | assertEquals(1, stepExecutions.size()); 75 | StepExecution stepExecution = stepExecutions.iterator().next(); 76 | assertEquals("AAA_0", stepExecution.getStepName()); 77 | } 78 | 79 | @Test 80 | public void testFailedGraph() { 81 | setupContextForGraph("failedStep && AAA"); 82 | Collection stepExecutions = getStepExecutions(); 83 | assertEquals(1, stepExecutions.size()); 84 | StepExecution stepExecution = stepExecutions.iterator().next(); 85 | assertEquals("failedStep_0", stepExecution.getStepName()); 86 | } 87 | 88 | @Test 89 | public void testEmbeddedFailedGraph() { 90 | setupContextForGraph("AAA && failedStep && BBB"); 91 | Collection stepExecutions = getStepExecutions(); 92 | assertEquals(2, stepExecutions.size()); 93 | List sortedStepExecution = 94 | getSortedStepExecutions(stepExecutions); 95 | assertEquals("AAA_0", sortedStepExecution.get(0).getStepName()); 96 | assertEquals("failedStep_0", sortedStepExecution.get(1).getStepName()); 97 | } 98 | 99 | @Ignore("Disabling till parser can support duplicate tasks") 100 | @Test 101 | public void duplicateTaskTest() { 102 | setupContextForGraph("AAA && AAA"); 103 | Collection stepExecutions = getStepExecutions(); 104 | assertEquals(2, stepExecutions.size()); 105 | List sortedStepExecution = 106 | getSortedStepExecutions(stepExecutions); 107 | assertEquals("AAA_1", sortedStepExecution.get(0).getStepName()); 108 | assertEquals("AAA_0", sortedStepExecution.get(1).getStepName()); 109 | 110 | } 111 | 112 | @Test 113 | public void testSequential() { 114 | setupContextForGraph("AAA && BBB && CCC"); 115 | List stepExecutions = getSortedStepExecutions(getStepExecutions()); 116 | assertEquals(3, stepExecutions.size()); 117 | Iterator iterator = stepExecutions.iterator(); 118 | StepExecution stepExecution = iterator.next(); 119 | assertEquals("AAA_0", stepExecution.getStepName()); 120 | stepExecution = iterator.next(); 121 | assertEquals("BBB_0", stepExecution.getStepName()); 122 | stepExecution = iterator.next(); 123 | assertEquals("CCC_0", stepExecution.getStepName()); 124 | } 125 | 126 | @Test 127 | public void splitTest() { 128 | setupContextForGraph(""); 129 | Collection stepExecutions = getStepExecutions(); 130 | Set stepNames = getStepNames(stepExecutions); 131 | assertEquals(3, stepExecutions.size()); 132 | assertTrue(stepNames.contains("AAA_0")); 133 | assertTrue(stepNames.contains("BBB_0")); 134 | assertTrue(stepNames.contains("CCC_0")); 135 | } 136 | 137 | @Test 138 | public void nestedSplit() { 139 | setupContextForGraph("< && CCC || DDD>", "--splitThreadCorePoolSize=5"); 140 | Collection stepExecutions = getStepExecutions(); 141 | Set stepNames = getStepNames(stepExecutions); 142 | assertEquals(4, stepExecutions.size()); 143 | assertTrue(stepNames.contains("AAA_0")); 144 | assertTrue(stepNames.contains("BBB_0")); 145 | assertTrue(stepNames.contains("CCC_0")); 146 | assertTrue(stepNames.contains("DDD_0")); 147 | } 148 | 149 | @Test 150 | public void nestedSplitThreadPoolSize() { 151 | Throwable exception = assertThrows(BeanCreationException.class, () -> 152 | setupContextForGraph("< && CCC || && FFF>", "--splitThreadCorePoolSize=1")); 153 | assertThat(exception.getCause().getCause().getMessage()).isEqualTo("Split thread core pool size 1 should be equal or greater than the " + 154 | "depth of split flows 3. Try setting the composed task property " + 155 | "`splitThreadCorePoolSize`"); 156 | } 157 | 158 | @Test 159 | public void twoSplitTest() { 160 | setupContextForGraph(" && "); 161 | Collection stepExecutions = getStepExecutions(); 162 | Set stepNames = getStepNames(stepExecutions); 163 | assertEquals(5, stepExecutions.size()); 164 | assertTrue(stepNames.contains("AAA_0")); 165 | assertTrue(stepNames.contains("BBB_0")); 166 | assertTrue(stepNames.contains("CCC_0")); 167 | assertTrue(stepNames.contains("DDD_0")); 168 | assertTrue(stepNames.contains("EEE_0")); 169 | } 170 | 171 | @Test 172 | public void testSequentialAndSplit() { 173 | setupContextForGraph("AAA && && EEE"); 174 | Collection stepExecutions = getStepExecutions(); 175 | Set stepNames = getStepNames(stepExecutions); 176 | assertEquals(5, stepExecutions.size()); 177 | assertTrue(stepNames.contains("AAA_0")); 178 | assertTrue(stepNames.contains("BBB_0")); 179 | assertTrue(stepNames.contains("CCC_0")); 180 | assertTrue(stepNames.contains("DDD_0")); 181 | assertTrue(stepNames.contains("EEE_0")); 182 | List sortedStepExecution = 183 | getSortedStepExecutions(stepExecutions); 184 | assertEquals("AAA_0", sortedStepExecution.get(0).getStepName()); 185 | assertEquals("EEE_0", sortedStepExecution.get(4).getStepName()); 186 | } 187 | 188 | @Test 189 | public void testSequentialTransitionAndSplit() { 190 | setupContextForGraph("AAA && FFF 'FAILED' -> EEE && && DDD"); 191 | Collection stepExecutions = getStepExecutions(); 192 | Set stepNames = getStepNames(stepExecutions); 193 | assertEquals(5, stepExecutions.size()); 194 | assertTrue(stepNames.contains("AAA_0")); 195 | assertTrue(stepNames.contains("BBB_0")); 196 | assertTrue(stepNames.contains("CCC_0")); 197 | assertTrue(stepNames.contains("DDD_0")); 198 | assertTrue(stepNames.contains("FFF_0")); 199 | List sortedStepExecution = 200 | getSortedStepExecutions(stepExecutions); 201 | assertEquals("AAA_0", sortedStepExecution.get(0).getStepName()); 202 | assertEquals("DDD_0", sortedStepExecution.get(4).getStepName()); 203 | } 204 | 205 | @Test 206 | public void testSequentialTransitionAndSplitFailedInvalid() { 207 | verifyExceptionThrown(INVALID_FLOW_MSG, 208 | "AAA && failedStep 'FAILED' -> EEE '*' -> FFF && && DDD"); 209 | } 210 | 211 | @Test 212 | public void testSequentialTransitionAndSplitFailed() { 213 | setupContextForGraph("AAA && failedStep 'FAILED' -> EEE && FFF && && DDD"); 214 | Collection stepExecutions = getStepExecutions(); 215 | Set stepNames = getStepNames(stepExecutions); 216 | assertEquals(3, stepExecutions.size()); 217 | assertTrue(stepNames.contains("AAA_0")); 218 | assertTrue(stepNames.contains("failedStep_0")); 219 | assertTrue(stepNames.contains("EEE_0")); 220 | } 221 | 222 | @Test 223 | public void testSequentialAndFailedSplit() { 224 | setupContextForGraph("AAA && && EEE"); 225 | Collection stepExecutions = getStepExecutions(); 226 | Set stepNames = getStepNames(stepExecutions); 227 | assertEquals(4, stepExecutions.size()); 228 | assertTrue(stepNames.contains("AAA_0")); 229 | assertTrue(stepNames.contains("BBB_0")); 230 | assertTrue(stepNames.contains("DDD_0")); 231 | assertTrue(stepNames.contains("failedStep_0")); 232 | } 233 | 234 | @Test 235 | public void testSequentialAndSplitWithFlow() { 236 | setupContextForGraph("AAA && && EEE"); 237 | Collection stepExecutions = getStepExecutions(); 238 | Set stepNames = getStepNames(stepExecutions); 239 | assertEquals(6, stepExecutions.size()); 240 | assertTrue(stepNames.contains("AAA_0")); 241 | assertTrue(stepNames.contains("BBB_0")); 242 | assertTrue(stepNames.contains("CCC_0")); 243 | assertTrue(stepNames.contains("DDD_0")); 244 | assertTrue(stepNames.contains("EEE_0")); 245 | assertTrue(stepNames.contains("FFF_0")); 246 | 247 | List sortedStepExecution = 248 | getSortedStepExecutions(stepExecutions); 249 | assertEquals("AAA_0", sortedStepExecution.get(0).getStepName()); 250 | assertEquals("EEE_0", sortedStepExecution.get(5).getStepName()); 251 | } 252 | 253 | @Test 254 | public void testFailedBasicTransition() { 255 | setupContextForGraph("failedStep 'FAILED' -> AAA * -> BBB"); 256 | Collection stepExecutions = getStepExecutions(); 257 | Set stepNames = getStepNames(stepExecutions); 258 | assertEquals(2, stepExecutions.size()); 259 | assertTrue(stepNames.contains("failedStep_0")); 260 | assertTrue(stepNames.contains("AAA_0")); 261 | } 262 | 263 | @Test 264 | public void testSuccessBasicTransition() { 265 | setupContextForGraph("AAA 'FAILED' -> BBB * -> CCC"); 266 | Collection stepExecutions = getStepExecutions(); 267 | Set stepNames = getStepNames(stepExecutions); 268 | assertEquals(2, stepExecutions.size()); 269 | assertTrue(stepNames.contains("AAA_0")); 270 | assertTrue(stepNames.contains("CCC_0")); 271 | } 272 | 273 | @Test 274 | public void testSuccessBasicTransitionWithSequence() { 275 | verifyExceptionThrown(INVALID_FLOW_MSG, 276 | "AAA 'FAILED' -> BBB * -> CCC && DDD && EEE"); 277 | } 278 | 279 | @Test 280 | public void testSuccessBasicTransitionWithTransition() { 281 | setupContextForGraph("AAA 'FAILED' -> BBB && CCC 'FAILED' -> DDD '*' -> EEE"); 282 | Collection stepExecutions = getStepExecutions(); 283 | Set stepNames = getStepNames(stepExecutions); 284 | assertEquals(3, stepExecutions.size()); 285 | assertTrue(stepNames.contains("AAA_0")); 286 | assertTrue(stepNames.contains("CCC_0")); 287 | assertTrue(stepNames.contains("EEE_0")); 288 | List sortedStepExecution = 289 | getSortedStepExecutions(stepExecutions); 290 | assertEquals("AAA_0", sortedStepExecution.get(0).getStepName()); 291 | assertEquals("EEE_0", sortedStepExecution.get(2).getStepName()); 292 | } 293 | 294 | @Test 295 | public void testSequenceFollowedBySuccessBasicTransitionSequence() { 296 | verifyExceptionThrown(INVALID_FLOW_MSG, 297 | "DDD && AAA 'FAILED' -> BBB * -> CCC && EEE"); 298 | } 299 | 300 | @Test 301 | public void testWildCardOnlyInLastPosition() { 302 | setupContextForGraph("AAA 'FAILED' -> BBB && CCC * -> DDD "); 303 | Collection stepExecutions = getStepExecutions(); 304 | Set stepNames = getStepNames(stepExecutions); 305 | assertEquals(3, stepExecutions.size()); 306 | assertTrue(stepNames.contains("AAA_0")); 307 | assertTrue(stepNames.contains("CCC_0")); 308 | assertTrue(stepNames.contains("DDD_0")); 309 | List sortedStepExecution = 310 | getSortedStepExecutions(stepExecutions); 311 | assertEquals("AAA_0", sortedStepExecution.get(0).getStepName()); 312 | assertEquals("DDD_0", sortedStepExecution.get(2).getStepName()); 313 | } 314 | 315 | 316 | @Test 317 | public void failedStepTransitionWithDuplicateTaskNameTest() { 318 | verifyExceptionThrown( 319 | "Problems found when validating 'failedStep " + 320 | "'FAILED' -> BBB && CCC && BBB && EEE': " + 321 | "[166E:(pos 38): duplicate app name. Use a " + 322 | "label to ensure uniqueness]", 323 | "failedStep 'FAILED' -> BBB && CCC && BBB && EEE"); 324 | } 325 | 326 | @Test 327 | public void successStepTransitionWithDuplicateTaskNameTest() { 328 | verifyExceptionThrown( 329 | "Problems found when validating 'AAA 'FAILED' -> " + 330 | "BBB * -> CCC && BBB && EEE': [166E:(pos 33): " + 331 | "duplicate app name. Use a label to ensure " + 332 | "uniqueness]", "AAA 'FAILED' -> BBB * -> CCC && BBB && EEE"); 333 | } 334 | 335 | 336 | private Set getStepNames(Collection stepExecutions) { 337 | Set result = new HashSet<>(); 338 | for (StepExecution stepExecution : stepExecutions) { 339 | result.add(stepExecution.getStepName()); 340 | } 341 | return result; 342 | } 343 | 344 | private void setupContextForGraph(String graph, String... args) { 345 | List argsForCtx = new ArrayList<>(Arrays.asList(args)); 346 | argsForCtx.add("--graph=" + graph); 347 | argsForCtx.add(CLOSE_CONTEXT_ARG); 348 | argsForCtx.add(TASK_NAME_ARG); 349 | setupContextForGraph(argsForCtx.toArray(new String[0])); 350 | } 351 | 352 | private void setupContextForGraph(String[] args) { 353 | this.applicationContext = SpringApplication.run(new Class[]{ComposedRunnerVisitorConfiguration.class, 354 | PropertyPlaceholderAutoConfiguration.class, 355 | EmbeddedDataSourceConfiguration.class, 356 | BatchAutoConfiguration.class, 357 | TaskBatchAutoConfiguration.class, 358 | SimpleTaskAutoConfiguration.class}, args); 359 | } 360 | 361 | private Collection getStepExecutions() { 362 | JobExplorer jobExplorer = this.applicationContext.getBean(JobExplorer.class); 363 | List jobInstances = jobExplorer.findJobInstancesByJobName("job", 0, 1); 364 | assertEquals(1, jobInstances.size()); 365 | JobInstance jobInstance = jobInstances.get(0); 366 | List jobExecutions = jobExplorer.getJobExecutions(jobInstance); 367 | assertEquals(1, jobExecutions.size()); 368 | JobExecution jobExecution = jobExecutions.get(0); 369 | return jobExecution.getStepExecutions(); 370 | } 371 | 372 | private List getSortedStepExecutions(Collection stepExecutions) { 373 | List result = new ArrayList<>(stepExecutions); 374 | result.sort(Comparator.comparing(StepExecution::getStartTime)); 375 | return result; 376 | } 377 | 378 | private void verifyExceptionThrown(String message, String graph) { 379 | Throwable exception = assertThrows(BeanCreationException.class, () -> setupContextForGraph(graph)); 380 | assertThat(exception.getCause().getCause().getMessage()).isEqualTo(message); 381 | } 382 | 383 | } 384 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunnerConfigurationJobIncrementerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | 22 | import org.springframework.batch.core.Job; 23 | import org.springframework.batch.core.JobParameters; 24 | import org.springframework.batch.core.repository.JobRepository; 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 27 | import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; 28 | import org.springframework.cloud.common.security.CommonSecurityAutoConfiguration; 29 | import org.springframework.cloud.common.security.IgnoreAllSecurityConfiguration; 30 | import org.springframework.cloud.task.app.composedtaskrunner.configuration.DataFlowTestConfiguration; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.test.annotation.DirtiesContext; 33 | import org.springframework.test.context.ContextConfiguration; 34 | import org.springframework.test.context.TestPropertySource; 35 | import org.springframework.test.context.junit4.SpringRunner; 36 | import org.springframework.util.Assert; 37 | 38 | /** 39 | * @author Glenn Renfro 40 | */ 41 | @RunWith(SpringRunner.class) 42 | @ContextConfiguration(classes={EmbeddedDataSourceConfiguration.class, 43 | DataFlowTestConfiguration.class,StepBeanDefinitionRegistrar.class, 44 | ComposedTaskRunnerConfiguration.class, 45 | StepBeanDefinitionRegistrar.class}) 46 | @EnableAutoConfiguration(exclude = { CommonSecurityAutoConfiguration.class}) 47 | @TestPropertySource(properties = {"graph=AAA && BBB && CCC","max-wait-time=1000", "increment-instance-enabled=true"}) 48 | public class ComposedTaskRunnerConfigurationJobIncrementerTests { 49 | 50 | @Autowired 51 | private JobRepository jobRepository; 52 | 53 | @Autowired 54 | protected Job job; 55 | 56 | @Test 57 | @DirtiesContext 58 | public void testComposedConfigurationWithJobIncrementer() throws Exception { 59 | this.jobRepository.createJobExecution( 60 | "ComposedTest", new JobParameters()); 61 | Assert.notNull(job.getJobParametersIncrementer(), "JobParametersIncrementer must not be null."); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunnerConfigurationNoPropertiesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import org.springframework.batch.core.Job; 26 | import org.springframework.batch.core.JobExecution; 27 | import org.springframework.batch.core.JobParameters; 28 | import org.springframework.batch.core.repository.JobRepository; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 31 | import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; 32 | import org.springframework.cloud.common.security.CommonSecurityAutoConfiguration; 33 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 34 | import org.springframework.cloud.task.app.composedtaskrunner.configuration.DataFlowTestConfiguration; 35 | import org.springframework.test.annotation.DirtiesContext; 36 | import org.springframework.test.context.ContextConfiguration; 37 | import org.springframework.test.context.TestPropertySource; 38 | import org.springframework.test.context.junit4.SpringRunner; 39 | import org.springframework.util.Assert; 40 | 41 | import static org.mockito.Mockito.verify; 42 | 43 | /** 44 | * @author Glenn Renfro 45 | */ 46 | @RunWith(SpringRunner.class) 47 | @ContextConfiguration(classes={EmbeddedDataSourceConfiguration.class, 48 | DataFlowTestConfiguration.class,StepBeanDefinitionRegistrar.class, 49 | ComposedTaskRunnerConfiguration.class, 50 | StepBeanDefinitionRegistrar.class}) 51 | @TestPropertySource(properties = {"graph=AAA && BBB && CCC","max-wait-time=1000"}) 52 | @EnableAutoConfiguration(exclude = { CommonSecurityAutoConfiguration.class}) 53 | public class ComposedTaskRunnerConfigurationNoPropertiesTests { 54 | 55 | @Autowired 56 | private JobRepository jobRepository; 57 | 58 | @Autowired 59 | private Job job; 60 | 61 | @Autowired 62 | private TaskOperations taskOperations; 63 | 64 | @Test 65 | @DirtiesContext 66 | public void testComposedConfiguration() throws Exception { 67 | JobExecution jobExecution = this.jobRepository.createJobExecution( 68 | "ComposedTest", new JobParameters()); 69 | job.execute(jobExecution); 70 | 71 | Assert.isNull(job.getJobParametersIncrementer(), "JobParametersIncrementer must be null."); 72 | verify(this.taskOperations).launch("AAA", new HashMap<>(0), new ArrayList<>(0), null); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunnerConfigurationWithPropertiesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | 27 | import org.springframework.batch.core.Job; 28 | import org.springframework.batch.core.JobExecution; 29 | import org.springframework.batch.core.JobParameters; 30 | import org.springframework.batch.core.repository.JobRepository; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 33 | import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; 34 | import org.springframework.cloud.common.security.CommonSecurityAutoConfiguration; 35 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 36 | import org.springframework.cloud.task.app.composedtaskrunner.configuration.DataFlowTestConfiguration; 37 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 38 | import org.springframework.test.annotation.DirtiesContext; 39 | import org.springframework.test.context.ContextConfiguration; 40 | import org.springframework.test.context.TestPropertySource; 41 | import org.springframework.test.context.junit4.SpringRunner; 42 | import org.springframework.util.Assert; 43 | 44 | import static org.junit.Assert.assertEquals; 45 | import static org.mockito.Mockito.verify; 46 | import static org.springframework.cloud.task.app.composedtaskrunner.ComposedTaskRunnerConfigurationWithPropertiesTests.COMPOSED_TASK_PROPS; 47 | 48 | /** 49 | * @author Glenn Renfro 50 | */ 51 | @RunWith(SpringRunner.class) 52 | @ContextConfiguration(classes={EmbeddedDataSourceConfiguration.class, 53 | DataFlowTestConfiguration.class,StepBeanDefinitionRegistrar.class, 54 | ComposedTaskRunnerConfiguration.class, 55 | StepBeanDefinitionRegistrar.class}) 56 | @TestPropertySource(properties = {"graph=AAA && BBB && CCC","max-wait-time=1010", 57 | "composed-task-properties=" + COMPOSED_TASK_PROPS , 58 | "interval-time-between-checks=1100", "composed-task-arguments=--baz=boo", 59 | "dataflow-server-uri=https://bar"}) 60 | @EnableAutoConfiguration(exclude = { CommonSecurityAutoConfiguration.class}) 61 | public class ComposedTaskRunnerConfigurationWithPropertiesTests { 62 | 63 | @Autowired 64 | private JobRepository jobRepository; 65 | 66 | @Autowired 67 | private Job job; 68 | 69 | @Autowired 70 | private TaskOperations taskOperations; 71 | 72 | @Autowired 73 | private ComposedTaskProperties composedTaskProperties; 74 | 75 | protected static final String COMPOSED_TASK_PROPS = "app.AAA.format=yyyy, " 76 | + "app.BBB.format=mm, " 77 | + "deployer.AAA.memory=2048m"; 78 | 79 | @Test 80 | @DirtiesContext 81 | public void testComposedConfiguration() throws Exception { 82 | JobExecution jobExecution = this.jobRepository.createJobExecution( 83 | "ComposedTest", new JobParameters()); 84 | job.execute(jobExecution); 85 | 86 | Map props = new HashMap<>(1); 87 | props.put("format", "yyyy"); 88 | props.put("memory", "2048m"); 89 | assertEquals(COMPOSED_TASK_PROPS, composedTaskProperties.getComposedTaskProperties()); 90 | assertEquals(1010, composedTaskProperties.getMaxWaitTime()); 91 | assertEquals(1100, composedTaskProperties.getIntervalTimeBetweenChecks()); 92 | assertEquals("https://bar", composedTaskProperties.getDataflowServerUri().toASCIIString()); 93 | List args = new ArrayList<>(1); 94 | args.add("--baz=boo"); 95 | Assert.isNull(job.getJobParametersIncrementer(), "JobParametersIncrementer must be null."); 96 | verify(this.taskOperations).launch("AAA", props, args, null); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskRunnerStepFactoryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import javax.sql.DataSource; 20 | 21 | import org.junit.Assert; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import org.springframework.batch.core.Step; 26 | import org.springframework.batch.core.StepExecutionListener; 27 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 28 | import org.springframework.batch.core.repository.JobRepository; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.boot.test.mock.mockito.MockBean; 31 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 32 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 33 | import org.springframework.cloud.task.configuration.TaskConfigurer; 34 | import org.springframework.cloud.task.configuration.TaskProperties; 35 | import org.springframework.cloud.task.repository.TaskExplorer; 36 | import org.springframework.cloud.task.repository.TaskRepository; 37 | import org.springframework.context.annotation.Bean; 38 | import org.springframework.context.annotation.Configuration; 39 | import org.springframework.test.context.ContextConfiguration; 40 | import org.springframework.test.context.junit4.SpringRunner; 41 | import org.springframework.transaction.PlatformTransactionManager; 42 | 43 | import static org.mockito.Mockito.mock; 44 | 45 | /** 46 | * @author Glenn Renfro 47 | */ 48 | @RunWith(SpringRunner.class) 49 | @ContextConfiguration(classes={ComposedTaskRunnerStepFactoryTests.StepFactoryConfiguration.class}) 50 | public class ComposedTaskRunnerStepFactoryTests { 51 | 52 | @Autowired 53 | ComposedTaskRunnerStepFactory stepFactory; 54 | 55 | @Test 56 | public void testStep() throws Exception{ 57 | Step step = stepFactory.getObject(); 58 | Assert.assertEquals("FOOBAR", step.getName()); 59 | Assert.assertEquals(Integer.MAX_VALUE, step.getStartLimit()); 60 | } 61 | 62 | @Configuration 63 | public static class StepFactoryConfiguration { 64 | 65 | @MockBean 66 | public StepExecutionListener composedTaskStepExecutionListener; 67 | 68 | @MockBean 69 | public TaskOperations taskOperations; 70 | 71 | @Bean 72 | public TaskProperties taskProperties() { 73 | return new TaskProperties(); 74 | } 75 | 76 | @Bean 77 | public StepBuilderFactory steps(){ 78 | return new StepBuilderFactory(mock(JobRepository.class), mock(PlatformTransactionManager.class)); 79 | } 80 | 81 | @Bean 82 | public TaskConfigurer taskConfigurer() { 83 | return new TaskConfigurer() { 84 | @Override 85 | public TaskRepository getTaskRepository() { 86 | return null; 87 | } 88 | 89 | @Override 90 | public PlatformTransactionManager getTransactionManager() { 91 | return null; 92 | } 93 | 94 | @Override 95 | public TaskExplorer getTaskExplorer() { 96 | return mock(TaskExplorer.class); 97 | } 98 | 99 | @Override 100 | public DataSource getTaskDataSource() { 101 | return mock(DataSource.class); 102 | } 103 | }; 104 | } 105 | 106 | @Bean 107 | public ComposedTaskRunnerStepFactory stepFactory(TaskProperties taskProperties) { 108 | return new ComposedTaskRunnerStepFactory(new ComposedTaskProperties(), "FOOBAR"); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/ComposedTaskStepExecutionListenerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.Date; 20 | 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | 24 | import org.springframework.batch.core.ExitStatus; 25 | import org.springframework.batch.core.JobExecution; 26 | import org.springframework.batch.core.StepExecution; 27 | import org.springframework.cloud.task.repository.TaskExecution; 28 | import org.springframework.cloud.task.repository.TaskExplorer; 29 | import org.springframework.test.util.ReflectionTestUtils; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.mockito.ArgumentMatchers.anyLong; 33 | import static org.mockito.Mockito.mock; 34 | import static org.mockito.Mockito.when; 35 | 36 | /** 37 | * @author Glenn Renfro 38 | */ 39 | public class ComposedTaskStepExecutionListenerTests { 40 | 41 | private TaskExplorer taskExplorer; 42 | 43 | private StepExecution stepExecution; 44 | 45 | private ComposedTaskStepExecutionListener taskListener; 46 | 47 | @Before 48 | public void setup() { 49 | this.taskExplorer = mock(TaskExplorer.class); 50 | this.stepExecution = getStepExecution(); 51 | this.taskListener = 52 | new ComposedTaskStepExecutionListener(this.taskExplorer); 53 | ReflectionTestUtils.setField(this.taskListener, "taskExplorer", this.taskExplorer); 54 | } 55 | 56 | @Test 57 | public void testSuccessfulRun() { 58 | TaskExecution taskExecution = getDefaultTaskExecution(0, null); 59 | when(this.taskExplorer.getTaskExecution(anyLong())).thenReturn(taskExecution); 60 | populateExecutionContext(111L); 61 | assertEquals(ExitStatus.COMPLETED, this.taskListener.afterStep(this.stepExecution)); 62 | } 63 | 64 | @Test 65 | public void testExitMessageRunSuccess() { 66 | ExitStatus expectedTaskStatus = new ExitStatus("TEST_EXIT_MESSAGE"); 67 | TaskExecution taskExecution = getDefaultTaskExecution(0, 68 | expectedTaskStatus.getExitCode()); 69 | when(this.taskExplorer.getTaskExecution(anyLong())).thenReturn(taskExecution); 70 | populateExecutionContext(111L); 71 | 72 | assertEquals(expectedTaskStatus, this.taskListener.afterStep(this.stepExecution)); 73 | } 74 | 75 | @Test 76 | public void testExitMessageRunFail() { 77 | ExitStatus expectedTaskStatus = new ExitStatus("TEST_EXIT_MESSAGE"); 78 | TaskExecution taskExecution = getDefaultTaskExecution(1, 79 | expectedTaskStatus.getExitCode()); 80 | when(this.taskExplorer.getTaskExecution(anyLong())).thenReturn(taskExecution); 81 | populateExecutionContext(111L); 82 | 83 | assertEquals(expectedTaskStatus, this.taskListener.afterStep(this.stepExecution)); 84 | } 85 | 86 | @Test 87 | public void testFailedRun() { 88 | TaskExecution taskExecution = getDefaultTaskExecution(1, null); 89 | when(this.taskExplorer.getTaskExecution(anyLong())).thenReturn(taskExecution); 90 | populateExecutionContext(111L); 91 | 92 | assertEquals(ExitStatus.FAILED, this.taskListener.afterStep(this.stepExecution)); 93 | } 94 | 95 | @Test(expected = IllegalArgumentException.class) 96 | public void testNullExecutionId() { 97 | TaskExecution taskExecution = new TaskExecution(); 98 | when(this.taskExplorer.getTaskExecution(anyLong())).thenReturn(taskExecution); 99 | populateExecutionContext(null); 100 | this.taskListener.afterStep(this.stepExecution); 101 | } 102 | 103 | private StepExecution getStepExecution() { 104 | final long JOB_EXECUTION_ID = 123L; 105 | final String STEP_NAME = "myTestStep"; 106 | 107 | JobExecution jobExecution = new JobExecution(JOB_EXECUTION_ID); 108 | return new StepExecution(STEP_NAME, jobExecution); 109 | } 110 | 111 | private void populateExecutionContext(Long taskExecutionId) { 112 | this.stepExecution.getExecutionContext().put("task-execution-id", 113 | taskExecutionId); 114 | } 115 | 116 | private TaskExecution getDefaultTaskExecution (int exitCode, 117 | String exitMessage) { 118 | TaskExecution taskExecution = new TaskExecution(); 119 | taskExecution.setExitMessage(exitMessage); 120 | taskExecution.setExitCode(exitCode); 121 | taskExecution.setEndTime(new Date()); 122 | return taskExecution; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/TaskLauncherTaskletTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner; 18 | 19 | import java.util.Date; 20 | import java.util.List; 21 | 22 | import javax.sql.DataSource; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.ArgumentMatchers; 28 | import org.mockito.Mockito; 29 | 30 | import org.springframework.batch.core.JobExecution; 31 | import org.springframework.batch.core.StepContribution; 32 | import org.springframework.batch.core.StepExecution; 33 | import org.springframework.batch.core.UnexpectedJobExecutionException; 34 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 35 | import org.springframework.batch.core.scope.context.ChunkContext; 36 | import org.springframework.batch.core.scope.context.StepContext; 37 | import org.springframework.batch.repeat.RepeatStatus; 38 | import org.springframework.beans.factory.annotation.Autowired; 39 | import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; 40 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 41 | import org.springframework.cloud.dataflow.rest.client.DataFlowClientException; 42 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 43 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 44 | import org.springframework.cloud.task.app.composedtaskrunner.support.TaskExecutionTimeoutException; 45 | import org.springframework.cloud.task.configuration.TaskProperties; 46 | import org.springframework.cloud.task.repository.TaskExecution; 47 | import org.springframework.cloud.task.repository.TaskExplorer; 48 | import org.springframework.cloud.task.repository.TaskRepository; 49 | import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; 50 | import org.springframework.cloud.task.repository.dao.TaskExecutionDao; 51 | import org.springframework.cloud.task.repository.support.SimpleTaskExplorer; 52 | import org.springframework.cloud.task.repository.support.SimpleTaskRepository; 53 | import org.springframework.cloud.task.repository.support.TaskExecutionDaoFactoryBean; 54 | import org.springframework.cloud.task.repository.support.TaskRepositoryInitializer; 55 | import org.springframework.context.annotation.Bean; 56 | import org.springframework.context.annotation.Configuration; 57 | import org.springframework.hateoas.Link; 58 | import org.springframework.hateoas.VndErrors; 59 | import org.springframework.test.annotation.DirtiesContext; 60 | import org.springframework.test.context.ContextConfiguration; 61 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 62 | import org.springframework.web.client.ResourceAccessException; 63 | 64 | import static junit.framework.TestCase.assertNull; 65 | import static org.junit.Assert.assertEquals; 66 | import static org.junit.jupiter.api.Assertions.assertThrows; 67 | import static org.mockito.Mockito.mock; 68 | 69 | /** 70 | * @author Glenn Renfro 71 | */ 72 | @RunWith(SpringJUnit4ClassRunner.class) 73 | @ContextConfiguration(classes={EmbeddedDataSourceConfiguration.class, 74 | TaskLauncherTaskletTests.TestConfiguration.class}) 75 | public class TaskLauncherTaskletTests { 76 | 77 | private static final String TASK_NAME = "testTask1_0"; 78 | 79 | @Autowired 80 | private DataSource dataSource; 81 | 82 | @Autowired 83 | private ComposedTaskProperties composedTaskProperties; 84 | 85 | @Autowired 86 | private TaskRepositoryInitializer taskRepositoryInitializer; 87 | 88 | @Autowired 89 | private JdbcTaskExecutionDao taskExecutionDao; 90 | 91 | private TaskOperations taskOperations; 92 | 93 | private TaskRepository taskRepository; 94 | 95 | private TaskExplorer taskExplorer; 96 | 97 | 98 | @Before 99 | public void setup() throws Exception{ 100 | this.taskRepositoryInitializer.setDataSource(this.dataSource); 101 | 102 | this.taskRepositoryInitializer.afterPropertiesSet(); 103 | this.taskOperations = mock(TaskOperations.class); 104 | TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBean = 105 | new TaskExecutionDaoFactoryBean(this.dataSource); 106 | this.taskRepository = new SimpleTaskRepository(taskExecutionDaoFactoryBean); 107 | this.taskExplorer = new SimpleTaskExplorer(taskExecutionDaoFactoryBean); 108 | this.composedTaskProperties.setIntervalTimeBetweenChecks(500); 109 | } 110 | 111 | @Test 112 | @DirtiesContext 113 | public void testTaskLauncherTasklet() throws Exception{ 114 | createCompleteTaskExecution(0); 115 | TaskLauncherTasklet taskLauncherTasklet = 116 | getTaskExecutionTasklet(); 117 | ChunkContext chunkContext = chunkContext(); 118 | mockReturnValForTaskExecution(1L); 119 | execute(taskLauncherTasklet, null, chunkContext); 120 | assertEquals(1L, chunkContext.getStepContext() 121 | .getStepExecution().getExecutionContext() 122 | .get("task-execution-id")); 123 | 124 | mockReturnValForTaskExecution(2L); 125 | chunkContext = chunkContext(); 126 | createCompleteTaskExecution(0); 127 | taskLauncherTasklet = getTaskExecutionTasklet(); 128 | execute(taskLauncherTasklet, null, chunkContext); 129 | assertEquals(2L, chunkContext.getStepContext() 130 | .getStepExecution().getExecutionContext() 131 | .get("task-execution-id")); 132 | } 133 | 134 | @Test 135 | @DirtiesContext 136 | public void testTaskLauncherTaskletWithTaskExecutionId() throws Exception{ 137 | createCompleteTaskExecution(0); 138 | TaskLauncherTasklet taskLauncherTasklet = 139 | getTaskExecutionTasklet(); 140 | ChunkContext chunkContext = chunkContext(); 141 | mockReturnValForTaskExecution(1L); 142 | execute(taskLauncherTasklet, null, chunkContext); 143 | assertEquals(1L, chunkContext.getStepContext() 144 | .getStepExecution().getExecutionContext() 145 | .get("task-execution-id")); 146 | assertNull(chunkContext.getStepContext() 147 | .getStepExecution().getExecutionContext() 148 | .get("task-arguments")); 149 | 150 | TaskProperties taskProperties = new TaskProperties(); 151 | taskProperties.setExecutionid(88l); 152 | mockReturnValForTaskExecution(2L); 153 | chunkContext = chunkContext(); 154 | createCompleteTaskExecution(0); 155 | taskLauncherTasklet = getTaskExecutionTasklet(taskProperties); 156 | taskLauncherTasklet.setArguments(null); 157 | execute(taskLauncherTasklet, null, chunkContext); 158 | assertEquals(2L, chunkContext.getStepContext() 159 | .getStepExecution().getExecutionContext() 160 | .get("task-execution-id")); 161 | assertEquals("--spring.cloud.task.parent-execution-id=88", ((List)chunkContext.getStepContext() 162 | .getStepExecution().getExecutionContext() 163 | .get("task-arguments")).get(0)); 164 | } 165 | 166 | @Test 167 | @DirtiesContext 168 | public void testTaskLauncherTaskletTimeout() { 169 | mockReturnValForTaskExecution(1L); 170 | this.composedTaskProperties.setMaxWaitTime(500); 171 | this.composedTaskProperties.setIntervalTimeBetweenChecks(1000); 172 | TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); 173 | ChunkContext chunkContext = chunkContext(); 174 | Throwable exception = assertThrows(TaskExecutionTimeoutException.class, () -> execute(taskLauncherTasklet, null, chunkContext)); 175 | Assertions.assertThat(exception.getMessage()).isEqualTo("Timeout occurred while " + 176 | "processing task with Execution Id 1"); 177 | } 178 | 179 | @Test 180 | @DirtiesContext 181 | public void testInvalidTaskName() { 182 | final String ERROR_MESSAGE = 183 | "Could not find task definition named " + TASK_NAME; 184 | VndErrors errors = new VndErrors("message", ERROR_MESSAGE, new Link("ref")); 185 | Mockito.doThrow(new DataFlowClientException(errors)) 186 | .when(this.taskOperations) 187 | .launch(ArgumentMatchers.anyString(), 188 | ArgumentMatchers.any(), 189 | ArgumentMatchers.any(), ArgumentMatchers.any()); 190 | TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); 191 | ChunkContext chunkContext = chunkContext(); 192 | Throwable exception = assertThrows(DataFlowClientException.class, 193 | () -> taskLauncherTasklet.execute(null, chunkContext)); 194 | Assertions.assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE); 195 | } 196 | 197 | @Test 198 | @DirtiesContext 199 | public void testNoDataFlowServer() { 200 | final String ERROR_MESSAGE = 201 | "I/O error on GET request for \"http://localhost:9393\": Connection refused; nested exception is java.net.ConnectException: Connection refused"; 202 | Mockito.doThrow(new ResourceAccessException(ERROR_MESSAGE)) 203 | .when(this.taskOperations).launch(ArgumentMatchers.anyString(), 204 | ArgumentMatchers.any(), 205 | ArgumentMatchers.any(), ArgumentMatchers.any()); 206 | TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); 207 | ChunkContext chunkContext = chunkContext(); 208 | Throwable exception = assertThrows(ResourceAccessException.class, 209 | () -> execute(taskLauncherTasklet, null, chunkContext)); 210 | Assertions.assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE); 211 | } 212 | 213 | @Test 214 | @DirtiesContext 215 | public void testTaskLauncherTaskletFailure() { 216 | mockReturnValForTaskExecution(1L); 217 | TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); 218 | ChunkContext chunkContext = chunkContext(); 219 | createCompleteTaskExecution(1); 220 | Throwable exception = assertThrows(UnexpectedJobExecutionException.class, 221 | () -> execute(taskLauncherTasklet, null, chunkContext)); 222 | Assertions.assertThat(exception.getMessage()).isEqualTo("Task returned a non zero exit code."); 223 | } 224 | 225 | private RepeatStatus execute(TaskLauncherTasklet taskLauncherTasklet, StepContribution contribution, 226 | ChunkContext chunkContext) throws Exception{ 227 | RepeatStatus status = taskLauncherTasklet.execute(contribution, chunkContext); 228 | if (!status.isContinuable()) { 229 | throw new IllegalStateException("Expected continuable status for the first execution."); 230 | } 231 | return taskLauncherTasklet.execute(contribution, chunkContext); 232 | 233 | } 234 | 235 | @Test 236 | @DirtiesContext 237 | public void testTaskLauncherTaskletNullResult() throws Exception { 238 | boolean isException = false; 239 | mockReturnValForTaskExecution(1L); 240 | TaskLauncherTasklet taskLauncherTasklet = getTaskExecutionTasklet(); 241 | ChunkContext chunkContext = chunkContext(); 242 | getCompleteTaskExecutionWithNull(); 243 | Throwable exception = assertThrows(UnexpectedJobExecutionException.class, 244 | () -> execute(taskLauncherTasklet, null, chunkContext)); 245 | Assertions.assertThat(exception.getMessage()).isEqualTo("Task returned a null exit code."); 246 | } 247 | 248 | private void createCompleteTaskExecution(int exitCode) { 249 | TaskExecution taskExecution = this.taskRepository.createTaskExecution(); 250 | this.taskRepository.completeTaskExecution(taskExecution.getExecutionId(), 251 | exitCode, new Date(), ""); 252 | } 253 | 254 | private TaskExecution getCompleteTaskExecutionWithNull() { 255 | TaskExecution taskExecution = this.taskRepository.createTaskExecution(); 256 | taskExecutionDao.completeTaskExecution(taskExecution.getExecutionId(), null, new Date(), "hello", "goodbye"); 257 | return taskExecution; 258 | } 259 | 260 | private TaskLauncherTasklet getTaskExecutionTasklet() { 261 | return getTaskExecutionTasklet(new TaskProperties()); 262 | } 263 | 264 | private TaskLauncherTasklet getTaskExecutionTasklet(TaskProperties taskProperties) { 265 | return new TaskLauncherTasklet(this.taskOperations, 266 | this.taskExplorer, this.composedTaskProperties, 267 | TASK_NAME, taskProperties); 268 | } 269 | 270 | private ChunkContext chunkContext () 271 | { 272 | final long JOB_EXECUTION_ID = 123L; 273 | final String STEP_NAME = "myTestStep"; 274 | 275 | JobExecution jobExecution = new JobExecution(JOB_EXECUTION_ID); 276 | StepExecution stepExecution = new StepExecution(STEP_NAME, jobExecution); 277 | StepContext stepContext = new StepContext(stepExecution); 278 | return new ChunkContext(stepContext); 279 | } 280 | 281 | private void mockReturnValForTaskExecution(long executionId) { 282 | Mockito.doReturn(executionId) 283 | .when(this.taskOperations) 284 | .launch(ArgumentMatchers.anyString(), 285 | ArgumentMatchers.any(), 286 | ArgumentMatchers.any(), ArgumentMatchers.any()); 287 | } 288 | 289 | @Configuration 290 | @EnableBatchProcessing 291 | @EnableConfigurationProperties(ComposedTaskProperties.class) 292 | public static class TestConfiguration { 293 | 294 | 295 | @Bean 296 | TaskRepositoryInitializer taskRepositoryInitializer() { 297 | return new TaskRepositoryInitializer(); 298 | } 299 | 300 | @Bean 301 | TaskExecutionDao taskExecutionDao(DataSource dataSource) { 302 | return new JdbcTaskExecutionDao(dataSource); 303 | } 304 | 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/configuration/ComposedRunnerVisitorConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner.configuration; 18 | 19 | import org.springframework.batch.core.ExitStatus; 20 | import org.springframework.batch.core.Step; 21 | import org.springframework.batch.core.StepContribution; 22 | import org.springframework.batch.core.StepExecution; 23 | import org.springframework.batch.core.StepExecutionListener; 24 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 25 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 26 | import org.springframework.batch.core.scope.context.ChunkContext; 27 | import org.springframework.batch.core.step.tasklet.Tasklet; 28 | import org.springframework.batch.repeat.RepeatStatus; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 31 | import org.springframework.cloud.task.app.composedtaskrunner.ComposedRunnerJobFactory; 32 | import org.springframework.cloud.task.app.composedtaskrunner.ComposedRunnerVisitor; 33 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 34 | import org.springframework.context.annotation.Bean; 35 | import org.springframework.context.annotation.Configuration; 36 | import org.springframework.core.task.TaskExecutor; 37 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 38 | import org.springframework.transaction.annotation.Isolation; 39 | import org.springframework.transaction.interceptor.DefaultTransactionAttribute; 40 | import org.springframework.transaction.interceptor.TransactionAttribute; 41 | 42 | /** 43 | * @author Glenn Renfro 44 | * @author Ilayaperumal Gopinathan 45 | */ 46 | @Configuration 47 | @EnableBatchProcessing 48 | @EnableConfigurationProperties(ComposedTaskProperties.class) 49 | public class ComposedRunnerVisitorConfiguration { 50 | 51 | @Autowired 52 | private StepBuilderFactory steps; 53 | 54 | @Autowired 55 | private ComposedTaskProperties composedTaskProperties; 56 | 57 | @Bean 58 | public ComposedRunnerJobFactory job() { 59 | return new ComposedRunnerJobFactory(this.composedTaskProperties); 60 | } 61 | 62 | @Bean 63 | public ComposedRunnerVisitor composedRunnerStack() { 64 | return new ComposedRunnerVisitor(); 65 | } 66 | 67 | @Bean 68 | public TaskExecutor taskExecutor() { 69 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 70 | taskExecutor.setCorePoolSize(this.composedTaskProperties.getSplitThreadCorePoolSize()); 71 | taskExecutor.setMaxPoolSize(this.composedTaskProperties.getSplitThreadMaxPoolSize()); 72 | taskExecutor.setKeepAliveSeconds(this.composedTaskProperties.getSplitThreadKeepAliveSeconds()); 73 | taskExecutor.setAllowCoreThreadTimeOut( 74 | this.composedTaskProperties.isSplitThreadAllowCoreThreadTimeout()); 75 | taskExecutor.setQueueCapacity(this.composedTaskProperties.getSplitThreadQueueCapacity()); 76 | taskExecutor.setWaitForTasksToCompleteOnShutdown( 77 | this.composedTaskProperties.isSplitThreadWaitForTasksToCompleteOnShutdown()); 78 | return taskExecutor; 79 | } 80 | 81 | @Bean 82 | public Step AAA_0() { 83 | return createTaskletStep("AAA_0"); 84 | } 85 | 86 | @Bean 87 | public Step AAA_1() { 88 | return createTaskletStep("AAA_1"); 89 | } 90 | 91 | @Bean 92 | public Step AAA_2() { 93 | return createTaskletStep("AAA_2"); 94 | } 95 | 96 | @Bean 97 | public Step BBB_0() { 98 | return createTaskletStep("BBB_0"); 99 | } 100 | 101 | @Bean 102 | public Step BBB_1() { 103 | return createTaskletStep("BBB_1"); 104 | } 105 | 106 | @Bean 107 | public Step CCC_0() { 108 | return createTaskletStep("CCC_0"); 109 | } 110 | 111 | @Bean 112 | public Step DDD_0() { 113 | return createTaskletStep("DDD_0"); 114 | } 115 | 116 | @Bean 117 | public Step EEE_0() { 118 | return createTaskletStep("EEE_0"); 119 | } 120 | 121 | @Bean 122 | public Step FFF_0() { 123 | return createTaskletStep("FFF_0"); 124 | } 125 | 126 | @Bean 127 | public Step LABELA() { 128 | return createTaskletStep("LABELA"); 129 | } 130 | 131 | 132 | @Bean 133 | public Step failedStep_0() { 134 | return createTaskletStepWithListener("failedStep_0", 135 | failedStepExecutionListener()); 136 | } 137 | 138 | @Bean 139 | public Step successStep() { 140 | return createTaskletStepWithListener("successStep", 141 | successStepExecutionListener()); 142 | } 143 | 144 | @Bean 145 | public StepExecutionListener failedStepExecutionListener() { 146 | return new StepExecutionListener() { 147 | @Override 148 | public void beforeStep(StepExecution stepExecution) { 149 | 150 | } 151 | 152 | @Override 153 | public ExitStatus afterStep(StepExecution stepExecution) { 154 | return ExitStatus.FAILED; 155 | } 156 | }; 157 | } 158 | 159 | @Bean 160 | public StepExecutionListener successStepExecutionListener() { 161 | return new StepExecutionListener() { 162 | @Override 163 | public void beforeStep(StepExecution stepExecution) { 164 | 165 | } 166 | 167 | @Override 168 | public ExitStatus afterStep(StepExecution stepExecution) { 169 | return ExitStatus.COMPLETED; 170 | } 171 | }; 172 | } 173 | 174 | private Step createTaskletStepWithListener(final String taskName, 175 | StepExecutionListener stepExecutionListener) { 176 | return this.steps.get(taskName) 177 | .tasklet(new Tasklet() { 178 | @Override 179 | public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { 180 | return RepeatStatus.FINISHED; 181 | } 182 | }) 183 | .transactionAttribute(getTransactionAttribute()) 184 | .listener(stepExecutionListener) 185 | .build(); 186 | } 187 | 188 | private Step createTaskletStep(final String taskName) { 189 | return this.steps.get(taskName) 190 | .tasklet(new Tasklet() { 191 | @Override 192 | public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { 193 | return RepeatStatus.FINISHED; 194 | } 195 | }) 196 | .transactionAttribute(getTransactionAttribute()) 197 | .build(); 198 | } 199 | /** 200 | * Using the default transaction attribute for the job will cause the 201 | * TaskLauncher not to see the latest state in the database but rather 202 | * what is in its transaction. By setting isolation to READ_COMMITTED 203 | * The task launcher can see latest state of the db. Since the changes 204 | * to the task execution are done by the tasks. 205 | 206 | * @return DefaultTransactionAttribute with isolation set to READ_COMMITTED. 207 | */ 208 | private TransactionAttribute getTransactionAttribute() { 209 | DefaultTransactionAttribute defaultTransactionAttribute = 210 | new DefaultTransactionAttribute(); 211 | defaultTransactionAttribute.setIsolationLevel( 212 | Isolation.READ_COMMITTED.value()); 213 | return defaultTransactionAttribute; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/configuration/DataFlowConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018-2019 the original author or authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.springframework.cloud.task.app.composedtaskrunner.configuration; 19 | 20 | import java.net.URISyntaxException; 21 | 22 | import org.junit.Test; 23 | 24 | import org.springframework.cloud.task.app.composedtaskrunner.DataFlowConfiguration; 25 | import org.springframework.cloud.task.app.composedtaskrunner.properties.ComposedTaskProperties; 26 | import org.springframework.test.util.ReflectionTestUtils; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.fail; 30 | 31 | /** 32 | * @author Gunnar Hillert 33 | */ 34 | public class DataFlowConfigurationTests { 35 | 36 | @Test 37 | public void testTaskOperationsConfiguredWithMissingPassword() throws URISyntaxException{ 38 | final ComposedTaskProperties composedTaskProperties = new ComposedTaskProperties(); 39 | composedTaskProperties.setDataflowServerUsername("foo"); 40 | final DataFlowConfiguration dataFlowConfiguration = new DataFlowConfiguration(); 41 | ReflectionTestUtils.setField(dataFlowConfiguration, "properties", composedTaskProperties); 42 | try { 43 | dataFlowConfiguration.taskOperations(dataFlowConfiguration.dataFlowOperations(null, null)); 44 | } 45 | catch (IllegalArgumentException e) { 46 | assertEquals("A username may be specified only together with a password", e.getMessage()); 47 | return; 48 | } 49 | fail("Expected an IllegalArgumentException to be thrown"); 50 | } 51 | 52 | @Test 53 | public void testTaskOperationsConfiguredWithMissingUsername() throws URISyntaxException{ 54 | final ComposedTaskProperties composedTaskProperties = new ComposedTaskProperties(); 55 | composedTaskProperties.setDataflowServerPassword("bar"); 56 | final DataFlowConfiguration dataFlowConfiguration = new DataFlowConfiguration(); 57 | ReflectionTestUtils.setField(dataFlowConfiguration, "properties", composedTaskProperties); 58 | try { 59 | dataFlowConfiguration.taskOperations(dataFlowConfiguration.dataFlowOperations(null, null)); 60 | } 61 | catch (IllegalArgumentException e) { 62 | assertEquals("A password may be specified only together with a username", e.getMessage()); 63 | return; 64 | } 65 | fail("Expected an IllegalArgumentException to be thrown"); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/configuration/DataFlowTestConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.task.app.composedtaskrunner.configuration; 18 | 19 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 20 | import org.springframework.cloud.dataflow.rest.client.TaskOperations; 21 | import org.springframework.cloud.dataflow.rest.client.config.DataFlowClientAutoConfiguration; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | import static org.mockito.Mockito.mock; 26 | 27 | /** 28 | * @author Glenn Renfro 29 | */ 30 | @Configuration 31 | @EnableAutoConfiguration(exclude = DataFlowClientAutoConfiguration.class) 32 | public class DataFlowTestConfiguration { 33 | 34 | @Bean 35 | public TaskOperations taskOperations() { 36 | return mock(TaskOperations.class); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/properties/ComposedTaskPropertiesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2017-2018 the original author or authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.springframework.cloud.task.app.composedtaskrunner.properties; 19 | 20 | import java.net.URI; 21 | import java.net.URISyntaxException; 22 | 23 | import org.junit.Test; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.junit.Assert.assertFalse; 27 | import static org.junit.Assert.assertNull; 28 | 29 | /** 30 | * @author Glenn Renfro 31 | * @author Gunnar Hillert 32 | */ 33 | public class ComposedTaskPropertiesTests { 34 | 35 | @Test 36 | public void testGettersAndSetters() throws URISyntaxException{ 37 | ComposedTaskProperties properties = new ComposedTaskProperties(); 38 | properties.setComposedTaskProperties("aaa"); 39 | properties.setComposedTaskArguments("bbb"); 40 | properties.setIntervalTimeBetweenChecks(12345); 41 | properties.setMaxWaitTime(6789); 42 | properties.setDataflowServerUri(new URI("http://test")); 43 | properties.setGraph("ddd"); 44 | properties.setDataflowServerUsername("foo"); 45 | properties.setDataflowServerPassword("bar"); 46 | properties.setDataflowServerAccessToken("foobar"); 47 | assertEquals("aaa", properties.getComposedTaskProperties()); 48 | assertEquals("bbb", properties.getComposedTaskArguments()); 49 | assertEquals(12345, properties.getIntervalTimeBetweenChecks()); 50 | assertEquals(6789, properties.getMaxWaitTime()); 51 | assertEquals("http://test", properties.getDataflowServerUri().toString()); 52 | assertEquals("ddd", properties.getGraph()); 53 | assertEquals("foo", properties.getDataflowServerUsername()); 54 | assertEquals("bar", properties.getDataflowServerPassword()); 55 | assertEquals("foobar", properties.getDataflowServerAccessToken()); 56 | } 57 | 58 | @Test 59 | public void testDataflowServerURIDefaults() { 60 | ComposedTaskProperties properties = new ComposedTaskProperties(); 61 | assertEquals("http://localhost:9393", properties.getDataflowServerUri().toString()); 62 | } 63 | 64 | @Test 65 | public void testThreadDefaults() { 66 | ComposedTaskProperties properties = new ComposedTaskProperties(); 67 | assertEquals(ComposedTaskProperties.SPLIT_THREAD_CORE_POOL_SIZE_DEFAULT, properties.getSplitThreadCorePoolSize()); 68 | assertEquals(ComposedTaskProperties.SPLIT_THREAD_KEEP_ALIVE_SECONDS_DEFAULT, properties.getSplitThreadKeepAliveSeconds()); 69 | assertEquals(ComposedTaskProperties.SPLIT_THREAD_MAX_POOL_SIZE_DEFAULT, properties.getSplitThreadMaxPoolSize()); 70 | assertEquals(ComposedTaskProperties.SPLIT_THREAD_QUEUE_CAPACITY_DEFAULT, properties.getSplitThreadQueueCapacity()); 71 | assertEquals("http://localhost:9393", properties.getDataflowServerUri().toString()); 72 | assertFalse(properties.isSplitThreadAllowCoreThreadTimeout()); 73 | assertFalse(properties.isSplitThreadWaitForTasksToCompleteOnShutdown()); 74 | assertNull(properties.getDataflowServerUsername()); 75 | assertNull(properties.getDataflowServerPassword()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /spring-cloud-starter-task-composedtaskrunner/src/test/java/org/springframework/cloud/task/app/composedtaskrunner/support/OnOAuth2ClientCredentialsEnabledTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.cloud.task.app.composedtaskrunner.support; 17 | 18 | import static org.hamcrest.Matchers.equalTo; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import org.junit.After; 22 | import org.junit.Test; 23 | import org.springframework.boot.test.util.TestPropertyValues; 24 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Conditional; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | /** 30 | * @author Gunnar Hillert 31 | */ 32 | public class OnOAuth2ClientCredentialsEnabledTests { 33 | 34 | private AnnotationConfigApplicationContext context; 35 | 36 | @After 37 | public void teardown() { 38 | if (this.context != null) { 39 | this.context.close(); 40 | } 41 | } 42 | 43 | @Test 44 | public void noPropertySet() throws Exception { 45 | this.context = load(Config.class); 46 | assertThat(context.containsBean("myBean"), equalTo(false)); 47 | } 48 | 49 | @Test 50 | public void propertyClientId() throws Exception { 51 | this.context = load(Config.class, "oauth2-client-credentials-client-id:12345"); 52 | assertThat(context.containsBean("myBean"), equalTo(true)); 53 | } 54 | 55 | @Test 56 | public void clientIdOnlyWithNoValue() throws Exception { 57 | this.context = load(Config.class, "oauth2-client-credentials-client-id:"); 58 | assertThat(context.containsBean("myBean"), equalTo(false)); 59 | } 60 | 61 | private AnnotationConfigApplicationContext load(Class config, String... env) { 62 | AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); 63 | TestPropertyValues.of(env).applyTo(context); 64 | context.register(config); 65 | context.refresh(); 66 | return context; 67 | } 68 | 69 | @Configuration 70 | @Conditional(OnOAuth2ClientCredentialsEnabled.class) 71 | public static class Config { 72 | @Bean 73 | public String myBean() { 74 | return "myBean"; 75 | } 76 | } 77 | } 78 | --------------------------------------------------------------------------------