Initial commit
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
21
.github/workflows/ci-maven-main.yml
vendored
21
.github/workflows/ci-maven-main.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: CI PRs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: CI PR Build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: adopt
|
||||
java-version: 17
|
||||
- run: mvn "-Dmaven.repo.local=.m2" -U -B package -s .settings.xml
|
||||
53
.github/workflows/deploy-docs.yml
vendored
Normal file
53
.github/workflows/deploy-docs.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Deploy Docs
|
||||
run-name: ${{ format('{0} ({1})', github.workflow, github.event.inputs.build-refname || 'all') }}
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-refname:
|
||||
description: Enter git refname to build (e.g., 5.7.x).
|
||||
required: false
|
||||
push:
|
||||
branches: docs-build
|
||||
env:
|
||||
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'spring-cloud'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 5
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up refname build
|
||||
if: github.event.inputs.build-refname
|
||||
run: |
|
||||
git fetch --depth 1 https://github.com/$GITHUB_REPOSITORY ${{ github.event.inputs.build-refname }}
|
||||
export BUILD_REFNAME=${{ github.event.inputs.build-refname }}
|
||||
echo "BUILD_REFNAME=$BUILD_REFNAME" >> $GITHUB_ENV
|
||||
export BUILD_VERSION=$(git cat-file --textconv FETCH_HEAD:pom.xml | python3 -c "import xml.etree.ElementTree as xml; from sys import stdin; print(xml.parse(stdin).getroot().find('{http://maven.apache.org/POM/4.0.0}version').text)")
|
||||
echo BUILD_VERSION=$BUILD_VERSION >> $GITHUB_ENV
|
||||
- name: Run Antora
|
||||
run: |
|
||||
./mvnw --no-transfer-progress -B antora
|
||||
- name: Publish Docs
|
||||
uses: rwinch/spring-doc-actions/rsync-antora-reference@httpdocs-path
|
||||
with:
|
||||
docs-username: ${{ secrets.DOCS_USERNAME }}
|
||||
docs-host: ${{ secrets.DOCS_HOST }}
|
||||
docs-ssh-key: ${{ secrets.DOCS_SSH_KEY }}
|
||||
docs-ssh-host-key: ${{ secrets.DOCS_SSH_HOST_KEY }}
|
||||
site-path: target/antora/site
|
||||
- name: Bust Cloudflare Cache
|
||||
uses: spring-io/spring-doc-actions/bust-cloudflare-antora-cache@v0.0.11
|
||||
with:
|
||||
context-root: spring-cloud-task
|
||||
cloudflare-zone-id: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
cloudflare-cache-token: ${{ secrets.CLOUDFLARE_CACHE_TOKEN }}
|
||||
46
.gitignore
vendored
46
.gitignore
vendored
@@ -1,36 +1,22 @@
|
||||
*~
|
||||
.#*
|
||||
*#
|
||||
*.sw*
|
||||
_site/
|
||||
.factorypath
|
||||
.gradletasknamecache
|
||||
.DS_Store
|
||||
/application.yml
|
||||
/application.properties
|
||||
asciidoctor.css
|
||||
atlassian-ide-plugin.xml
|
||||
bin/
|
||||
build/
|
||||
dump.rdb
|
||||
out
|
||||
spring-shell.log
|
||||
target/
|
||||
test-output
|
||||
result.txt
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
.project
|
||||
.classpath
|
||||
*.orig
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IDEA artifacts and output dirs
|
||||
.factorypath
|
||||
.sts4-cache
|
||||
.ant-targets-build.xml
|
||||
src/ant/.ant-targets-upload-dist.xml
|
||||
*.sonar4clipse*
|
||||
.DS_Store
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/*
|
||||
|
||||
# Github Actions
|
||||
.m2
|
||||
/.idea/
|
||||
*.graphml
|
||||
node
|
||||
node_modules
|
||||
build
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
@@ -1 +1 @@
|
||||
-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom
|
||||
-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom
|
||||
@@ -1 +0,0 @@
|
||||
-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/snapshot -P spring
|
||||
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
||||
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Binary file not shown.
3
.mvn/wrapper/maven-wrapper.properties
vendored
3
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1 +1,2 @@
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ /*
|
||||
~ * Copyright 2016-2019 the original author or authors.
|
||||
~ *
|
||||
~ * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ * you may not use this file except in compliance with the License.
|
||||
~ * You may obtain a copy of the License at
|
||||
~ *
|
||||
~ * https://www.apache.org/licenses/LICENSE-2.0
|
||||
~ *
|
||||
~ * Unless required by applicable law or agreed to in writing, software
|
||||
~ * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ * See the License for the specific language governing permissions and
|
||||
~ * limitations under the License.
|
||||
~ */
|
||||
-->
|
||||
|
||||
<settings>
|
||||
<servers>
|
||||
<server>
|
||||
<id>repo.spring.io</id>
|
||||
<username>${env.CI_DEPLOY_USERNAME}</username>
|
||||
<password>${env.CI_DEPLOY_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
<profiles>
|
||||
<profile>
|
||||
<!--
|
||||
N.B. this profile is only here to support users and IDEs that do not use Maven 3.3.
|
||||
It isn't needed on the command line if you use the wrapper script (mvnw) or if you use
|
||||
a native Maven with the right version. Eclipse users should points their Maven tooling to
|
||||
this settings file, or copy the profile into their ~/.m2/settings.xml.
|
||||
-->
|
||||
<id>spring</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<pluginGroups>
|
||||
<pluginGroup>io.spring.javaformat</pluginGroup>
|
||||
</pluginGroups>
|
||||
</settings>
|
||||
@@ -1,44 +0,0 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
202
LICENSE
202
LICENSE
@@ -1,202 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
60
README.adoc
60
README.adoc
@@ -1,59 +1,23 @@
|
||||
////
|
||||
DO NOT EDIT THIS FILE. IT WAS GENERATED.
|
||||
Manual changes to this file will be lost when it is generated again.
|
||||
Edit the files in the src/main/asciidoc/ directory instead.
|
||||
////
|
||||
= Spring Cloud Task Docs Build
|
||||
|
||||
You're currently viewing the Antora playbook branch.
|
||||
The playbook branch hosts the docs build that is used to build and publish the production docs site.
|
||||
|
||||
= Spring Cloud Task
|
||||
The Spring Cloud Task reference docs are built using https://antora.org[Antora].
|
||||
This README covers how to build the docs in a software branch as well as how to build the production docs site locally.
|
||||
|
||||
Is a project centered around the idea of processing on demand. A user is able to develop
|
||||
a “task” that can be deployed, executed and removed on demand, yet the result of the
|
||||
process persists beyond the life of the task for future reporting.
|
||||
== Building the Site
|
||||
|
||||
You can build the entire site by invoking the following on the docs-build branch and then viewing the site at `target/site/index.html`
|
||||
|
||||
== Requirements:
|
||||
|
||||
* Java 17 or Above
|
||||
|
||||
== Build Main Project:
|
||||
|
||||
[source,shell,indent=2]
|
||||
[source,bash]
|
||||
----
|
||||
$ ./mvnw clean install
|
||||
./mvnw antora
|
||||
----
|
||||
|
||||
== Example:
|
||||
== Building a Specific Branch
|
||||
|
||||
[source,java,indent=2]
|
||||
[source,bash]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@EnableTask
|
||||
public class MyApp {
|
||||
|
||||
@Bean
|
||||
public MyTaskApplication myTask() {
|
||||
return new MyTaskApplication();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MyApp.class);
|
||||
}
|
||||
|
||||
public static class MyTaskApplication implements ApplicationRunner {
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
System.out.println("Hello World");
|
||||
}
|
||||
}
|
||||
}
|
||||
./mvnw antora
|
||||
----
|
||||
|
||||
== Code of Conduct
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
== Building the Project
|
||||
|
||||
This project requires that you invoke the Javadoc engine from the Maven command line. You can do so by appending `javadoc:aggregate` to the rest of your Maven command.
|
||||
For example, to build the entire project, you could use `mvn clean install -DskipTests -P docs`.
|
||||
|
||||
@@ -6,38 +6,39 @@ antora:
|
||||
- '@antora/collector-extension'
|
||||
- '@antora/atlas-extension'
|
||||
- require: '@springio/antora-extensions/root-component-extension'
|
||||
root_component_name: 'PROJECT_WITHOUT_SPRING'
|
||||
# FIXME: Run antora once using this extension to migrate to the Asciidoc Tabs syntax
|
||||
# and then remove this extension
|
||||
- require: '@springio/antora-extensions/tabs-migration-extension'
|
||||
unwrap_example_block: always
|
||||
save_result: true
|
||||
root_component_name: 'cloud-task'
|
||||
site:
|
||||
title: PROJECT_FULL_NAME
|
||||
url: https://docs.spring.io/PROJECT_NAME/reference/
|
||||
title: Spring Cloud Task
|
||||
url: https://docs.spring.io/spring-cloud-task/reference
|
||||
robots: allow
|
||||
git:
|
||||
ensure_git_suffix: false
|
||||
content:
|
||||
sources:
|
||||
- url: ./..
|
||||
branches: HEAD
|
||||
- url: https://github.com/spring-cloud/spring-cloud-task
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: [ main ]
|
||||
tags: [ '({4..9}).+({1..9}).+({0..9})?(-{RC,M}+({0..9}))', '!4.1.0-M1' ]
|
||||
start_path: docs
|
||||
worktrees: true
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-stackoverflow-url: https://stackoverflow.com/tags/spring-cloud
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
chomp: 'all'
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
sourcemap: true
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip
|
||||
snapshot: true
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
format: pretty
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip
|
||||
32
docs/.github/workflows/deploy-docs.yml
vendored
32
docs/.github/workflows/deploy-docs.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [ gh-pages ]
|
||||
tags: '**'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
#schedule:
|
||||
#- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
# if: github.repository_owner == 'spring-cloud'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
|
||||
- name: Dispatch (full build)
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
|
||||
@@ -1,12 +0,0 @@
|
||||
name: PROJECT_WITHOUT_SPRING
|
||||
version: true
|
||||
title: PROJECT_NAME
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: ./mvnw --no-transfer-progress -B process-resources -Pdocs -pl docs -Dantora-maven-plugin.phase=none -Dgenerate-docs.phase=none -Dgenerate-readme.phase=none -Dgenerate-cloud-resources.phase=none -Dmaven-dependency-plugin-for-docs.phase=none -Dmaven-dependency-plugin-for-docs-classes.phase=none -DskipTests
|
||||
local: true
|
||||
scan:
|
||||
dir: ./target/classes/antora-resources/
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@@ -1,16 +0,0 @@
|
||||
* xref:index.adoc[]
|
||||
* xref:spring-cloud-task.adoc[]
|
||||
* xref:_attributes.adoc[]
|
||||
* xref:preface.adoc[]
|
||||
* xref:getting-started.adoc[]
|
||||
* xref:features.adoc[]
|
||||
* xref:batch.adoc[]
|
||||
* xref:batch-starter.adoc[]
|
||||
* xref:stream.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* xref:appendix-task-repository-schema.adoc[]
|
||||
* xref:appendix-building-the-documentation.adoc[]
|
||||
* xref:_observability.adoc[]
|
||||
* xref:README.adoc[]
|
||||
* xref:_configprops.adoc[]
|
||||
* xref:sagan-index.adoc[]
|
||||
@@ -1,58 +0,0 @@
|
||||
[[spring-cloud-task]]
|
||||
= Spring Cloud Task
|
||||
|
||||
Is a project centered around the idea of processing on demand. A user is able to develop
|
||||
a “task” that can be deployed, executed and removed on demand, yet the result of the
|
||||
process persists beyond the life of the task for future reporting.
|
||||
|
||||
|
||||
[[requirements:]]
|
||||
== Requirements:
|
||||
|
||||
* Java 17 or Above
|
||||
|
||||
[[build-main-project:]]
|
||||
== Build Main Project:
|
||||
|
||||
[source,shell,indent=2]
|
||||
----
|
||||
$ ./mvnw clean install
|
||||
----
|
||||
|
||||
[[example:]]
|
||||
== Example:
|
||||
|
||||
[source,java,indent=2]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@EnableTask
|
||||
public class MyApp {
|
||||
|
||||
@Bean
|
||||
public MyTaskApplication myTask() {
|
||||
return new MyTaskApplication();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MyApp.class);
|
||||
}
|
||||
|
||||
public static class MyTaskApplication implements ApplicationRunner {
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
System.out.println("Hello World");
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[code-of-conduct]]
|
||||
== Code of Conduct
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
[[building-the-project]]
|
||||
== Building the Project
|
||||
|
||||
This project requires that you invoke the Javadoc engine from the Maven command line. You can do so by appending `javadoc:aggregate` to the rest of your Maven command.
|
||||
For example, to build the entire project, you could use `mvn clean install -DskipTests -P docs`.
|
||||
@@ -1,45 +0,0 @@
|
||||
:doctype: book
|
||||
:idprefix:
|
||||
:idseparator: -
|
||||
:tabsize: 4
|
||||
:numbered:
|
||||
:sectanchors:
|
||||
:sectnums:
|
||||
:icons: font
|
||||
:hide-uri-scheme:
|
||||
:docinfo: shared,private
|
||||
|
||||
:sc-ext: java
|
||||
:project-full-name: Spring Cloud Task
|
||||
|
||||
// project-specific attributes
|
||||
:spring-cloud-task-repo: snapshot
|
||||
:github-tag: master
|
||||
:spring-cloud-task-docs-version: current
|
||||
:spring-cloud-task-docs: https://docs.spring.io/spring-cloud-task/docs/{spring-cloud-task-docs-version}/reference
|
||||
:spring-cloud-task-docs-current: https://docs.spring.io/spring-cloud-task/docs/current-SNAPSHOT/reference/html/
|
||||
:github-repo: spring-cloud/spring-cloud-task
|
||||
:github-raw: https://raw.github.com/{github-repo}/{github-tag}
|
||||
:github-code: https://github.com/{github-repo}/tree/{github-tag}
|
||||
:github-wiki: https://github.com/{github-repo}/wiki
|
||||
:github-master-code: https://github.com/{github-repo}/tree/master
|
||||
:sc-ext: java
|
||||
:sc-spring-boot: {github-code}/spring-boot/src/main/java/org/springframework/boot
|
||||
:dc-ext: html
|
||||
:dc-root: https://docs.spring.io/spring-cloud-task/docs/{spring-cloud-dataflow-docs-version}/api
|
||||
:dc-spring-boot: {dc-root}/org/springframework/boot
|
||||
:dependency-management-plugin: https://github.com/spring-gradle-plugins/dependency-management-plugin
|
||||
:dependency-management-plugin-documentation: {dependency-management-plugin}/blob/master/README.md
|
||||
:spring-boot-maven-plugin-site: https://docs.spring.io/spring-boot/docs/{spring-boot-docs-version}/maven-plugin
|
||||
:spring-reference: https://docs.spring.io/spring/docs/{spring-docs-version}/spring-framework-reference/htmlsingle
|
||||
:spring-security-reference: https://docs.spring.io/spring-security/site/docs/{spring-security-docs-version}/reference/htmlsingle
|
||||
:spring-javadoc: https://docs.spring.io/spring/docs/{spring-docs-version}/javadoc-api/org/springframework
|
||||
:spring-amqp-javadoc: https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp
|
||||
:spring-data-javadoc: https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa
|
||||
:spring-data-commons-javadoc: https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data
|
||||
:spring-data-mongo-javadoc: https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb
|
||||
:spring-data-rest-javadoc: https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest
|
||||
:gradle-userguide: https://www.gradle.org/docs/current/userguide
|
||||
:propdeps-plugin: https://github.com/spring-projects/gradle-plugins/tree/master/propdeps-plugin
|
||||
:ant-manual: https://ant.apache.org/manual
|
||||
:attributes: allow-uri-read
|
||||
@@ -1,44 +0,0 @@
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|spring.cloud.task.batch.application-runner-order | `+++0+++` | The order for the {@code ApplicationRunner} used to run batch jobs when {@code spring.cloud.task.batch.fail-on-job-failure=true}. Defaults to 0 (same as the {@link org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner}).
|
||||
|spring.cloud.task.batch.command-line-runner-order | |
|
||||
|spring.cloud.task.batch.events.chunk-event-binding-name | `+++chunk-events+++` |
|
||||
|spring.cloud.task.batch.events.chunk-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.ChunkListener}.
|
||||
|spring.cloud.task.batch.events.chunk.enabled | `+++true+++` | This property is used to determine if a task should listen for batch chunk events.
|
||||
|spring.cloud.task.batch.events.enabled | `+++true+++` | This property is used to determine if a task should listen for batch events.
|
||||
|spring.cloud.task.batch.events.item-process-event-binding-name | `+++item-process-events+++` |
|
||||
|spring.cloud.task.batch.events.item-process-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.ItemProcessListener}.
|
||||
|spring.cloud.task.batch.events.item-process.enabled | `+++true+++` | This property is used to determine if a task should listen for batch item processed events.
|
||||
|spring.cloud.task.batch.events.item-read-event-binding-name | `+++item-read-events+++` |
|
||||
|spring.cloud.task.batch.events.item-read-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.ItemReadListener}.
|
||||
|spring.cloud.task.batch.events.item-read.enabled | `+++true+++` | This property is used to determine if a task should listen for batch item read events.
|
||||
|spring.cloud.task.batch.events.item-write-event-binding-name | `+++item-write-events+++` |
|
||||
|spring.cloud.task.batch.events.item-write-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.ItemWriteListener}.
|
||||
|spring.cloud.task.batch.events.item-write.enabled | `+++true+++` | This property is used to determine if a task should listen for batch item write events.
|
||||
|spring.cloud.task.batch.events.job-execution-event-binding-name | `+++job-execution-events+++` |
|
||||
|spring.cloud.task.batch.events.job-execution-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.JobExecutionListener}.
|
||||
|spring.cloud.task.batch.events.job-execution.enabled | `+++true+++` | This property is used to determine if a task should listen for batch job execution events.
|
||||
|spring.cloud.task.batch.events.skip-event-binding-name | `+++skip-events+++` |
|
||||
|spring.cloud.task.batch.events.skip-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.SkipListener}.
|
||||
|spring.cloud.task.batch.events.skip.enabled | `+++true+++` | This property is used to determine if a task should listen for batch skip events.
|
||||
|spring.cloud.task.batch.events.step-execution-event-binding-name | `+++step-execution-events+++` |
|
||||
|spring.cloud.task.batch.events.step-execution-order | | Establishes the default {@link Ordered} precedence for {@link org.springframework.batch.core.StepExecutionListener}.
|
||||
|spring.cloud.task.batch.events.step-execution.enabled | `+++true+++` | This property is used to determine if a task should listen for batch step execution events.
|
||||
|spring.cloud.task.batch.events.task-event-binding-name | `+++task-events+++` |
|
||||
|spring.cloud.task.batch.fail-on-job-failure | `+++false+++` | This property is used to determine if a task app should return with a non zero exit code if a batch job fails.
|
||||
|spring.cloud.task.batch.fail-on-job-failure-poll-interval | `+++5000+++` | Fixed delay in milliseconds that Spring Cloud Task will wait when checking if {@link org.springframework.batch.core.JobExecution}s have completed, when spring.cloud.task.batch.failOnJobFailure is set to true. Defaults to 5000.
|
||||
|spring.cloud.task.batch.job-names | | Comma-separated list of job names to execute on startup (for instance, `job1,job2`). By default, all Jobs found in the context are executed. @deprecated use spring.batch.job.name instead of spring.cloud.task.batch.jobNames.
|
||||
|spring.cloud.task.batch.listener.enabled | `+++true+++` | This property is used to determine if a task will be linked to the batch jobs that are run.
|
||||
|spring.cloud.task.closecontext-enabled | `+++false+++` | When set to true the context is closed at the end of the task. Else the context remains open.
|
||||
|spring.cloud.task.events.enabled | `+++true+++` | This property is used to determine if a task app should emit task events.
|
||||
|spring.cloud.task.executionid | | An id that will be used by the task when updating the task execution.
|
||||
|spring.cloud.task.external-execution-id | | An id that can be associated with a task.
|
||||
|spring.cloud.task.initialize-enabled | | If set to true then tables are initialized. If set to false tables are not initialized. Defaults to null. The requirement for it to be defaulted to null is so that we can support the <code>spring.cloud.task.initialize.enable</code> until it is removed.
|
||||
|spring.cloud.task.parent-execution-id | | The id of the parent task execution id that launched this task execution. Defaults to null if task execution had no parent.
|
||||
|spring.cloud.task.single-instance-enabled | `+++false+++` | This property is used to determine if a task will execute if another task with the same app name is running.
|
||||
|spring.cloud.task.single-instance-lock-check-interval | `+++500+++` | Declares the time (in millis) that a task execution will wait between checks. Default time is: 500 millis.
|
||||
|spring.cloud.task.single-instance-lock-ttl | | Declares the maximum amount of time (in millis) that a task execution can hold a lock to prevent another task from executing with a specific task name when the single-instance-enabled is set to true. Default time is: Integer.MAX_VALUE.
|
||||
|spring.cloud.task.table-prefix | `+++TASK_+++` | The prefix to append to the table names created by Spring Cloud Task.
|
||||
|
||||
|===
|
||||
@@ -1,9 +0,0 @@
|
||||
:root-target: ../../../target/
|
||||
|
||||
[[observability]]
|
||||
= Observability metadata
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
include::{root-target}_metrics.adoc[]
|
||||
|
||||
include::{root-target}_spans.adoc[]
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
[[appendix-building-the-documentation]]
|
||||
= Building This Documentation
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This project uses Maven to generate this documentation. To generate it for yourself,
|
||||
run the following command: `$ mvn clean install -DskipTests -P docs`.
|
||||
@@ -1,100 +0,0 @@
|
||||
[[appendix-task-repository-schema]]
|
||||
= Task Repository Schema
|
||||
|
||||
[[partintro]]
|
||||
--
|
||||
This appendix provides an ERD for the database schema used in the task repository.
|
||||
--
|
||||
|
||||
image::task_schema.png[]
|
||||
|
||||
[[table-information]]
|
||||
== Table Information
|
||||
--
|
||||
|
||||
.TASK_EXECUTION
|
||||
Stores the task execution information.
|
||||
[width="80%", cols="1,1,1,1,10", options="header"]
|
||||
|=========================================================
|
||||
|Column Name |Required |Type |Field Length |Notes
|
||||
|
||||
|TASK_EXECUTION_ID |TRUE |BIGINT | X |
|
||||
Spring Cloud Task Framework at app startup establishes the next available id as obtained from the `TASK_SEQ`. Or if the record is created outside of task then the value must be populated at record creation time.
|
||||
|
||||
|START_TIME |FALSE | DATETIME(6) | X | Spring Cloud Task Framework at app startup establishes the value.
|
||||
|
||||
|END_TIME |FALSE | DATETIME(6) | X | Spring Cloud Task Framework at app exit establishes the value.
|
||||
|
||||
|TASK_NAME |FALSE | VARCHAR | 100 | Spring Cloud Task Framework at app startup will set this to "Application" unless user establish the name using the `spring.application.name`.
|
||||
|
||||
|EXIT_CODE |FALSE | INTEGER | X | Follows Spring Boot defaults unless overridden by the user as discussed https://docs.spring.io/spring-cloud-task/docs/current/reference/#features-lifecycle-exit-codes[here].
|
||||
|
||||
|EXIT_MESSAGE |FALSE | VARCHAR | 2500 | User Defined as discussed https://docs.spring.io/spring-cloud-task/docs/current/reference/#features-task-execution-listener-exit-messages[here].
|
||||
|
||||
|ERROR_MESSAGE |FALSE | VARCHAR | 2500 | Spring Cloud Task Framework at app exit establishes the value.
|
||||
|
||||
|LAST_UPDATED |TRUE | TIMESTAMP | X | Spring Cloud Task Framework at app startup establishes the value. Or if the record is created outside of task then the value must be populated at record creation time.
|
||||
|
||||
|EXTERNAL_EXECUTION_ID |FALSE | VARCHAR | 250 | If the `spring.cloud.task.external-execution-id` property is set then Spring Cloud Task Framework at app startup will set this to the value specified. More information can be found xref:features.adoc#features-external_task_id[here]
|
||||
|
||||
|PARENT_TASK_EXECUTION_ID |FALSE |BIGINT | X | If the `spring.cloud.task.parent-execution-id` property is set then Spring Cloud Task Framework at app startup will set this to the value specified. More information can be found xref:features.adoc#features-parent_task_id[here]
|
||||
|
||||
|=========================================================
|
||||
|
||||
.TASK_EXECUTION_PARAMS
|
||||
Stores the parameters used for a task execution
|
||||
[width="80%", cols="1,1,1,1", options="header"]
|
||||
|=========================================================
|
||||
|Column Name |Required |Type |Field Length
|
||||
|TASK_EXECUTION_ID |TRUE |BIGINT | X
|
||||
|
||||
|TASK_PARAM |FALSE | VARCHAR | 2500
|
||||
|
||||
|=========================================================
|
||||
|
||||
.TASK_TASK_BATCH
|
||||
Used to link the task execution to the batch execution.
|
||||
[width="80%", cols="1,1,1,1", options="header"]
|
||||
|=========================================================
|
||||
|Column Name |Required |Type |Field Length
|
||||
|
||||
|TASK_EXECUTION_ID |TRUE |BIGINT | X
|
||||
|
||||
|JOB_EXECUTION_ID |TRUE | BIGINT | X
|
||||
|
||||
|=========================================================
|
||||
|
||||
|
||||
.TASK_LOCK
|
||||
Used for the `single-instance-enabled` feature discussed xref:features.adoc#features-single-instance-enabled[here].
|
||||
[width="80%", cols="1,1,1,1,10", options="header"]
|
||||
|=========================================================
|
||||
|Column Name |Required |Type | Field Length |Notes
|
||||
|
||||
|LOCK_KEY |TRUE |CHAR | 36 | UUID for the this lock
|
||||
|
||||
|REGION |TRUE | VARCHAR | 100 | User can establish a group of locks using this field.
|
||||
|
||||
|CLIENT_ID |TRUE | CHAR | 36 | The task execution id that contains the name of the app to lock.
|
||||
|
||||
|CREATED_DATE |TRUE | DATETIME | X | The date that the entry was created
|
||||
|
||||
|=========================================================
|
||||
|
||||
|
||||
NOTE: The DDL for setting up tables for each database type can be found https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task[here].
|
||||
--
|
||||
|
||||
[[sql-server]]
|
||||
== SQL Server
|
||||
By default Spring Cloud Task uses a sequence table for determining the `TASK_EXECUTION_ID` for the `TASK_EXECUTION` table.
|
||||
However, when launching multiple tasks simultaneously while using SQL Server, this can cause a deadlock to occur on the `TASK_SEQ` table.
|
||||
The resolution is to drop the `TASK_EXECUTION_SEQ` table and create a sequence using the same name. For example:
|
||||
```
|
||||
DROP TABLE TASK_SEQ;
|
||||
|
||||
CREATE SEQUENCE [DBO].[TASK_SEQ] AS BIGINT
|
||||
START WITH 1
|
||||
INCREMENT BY 1;
|
||||
```
|
||||
NOTE: Set the `START WITH` to a higher value than your current execution id.
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
[[appendix]]
|
||||
= Appendices
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
|
||||
|
||||
ifndef::train-docs[]
|
||||
endif::[]
|
||||
|
||||
@@ -1,655 +0,0 @@
|
||||
|
||||
[[batch-job-starter]]
|
||||
= Single Step Batch Job Starter
|
||||
|
||||
[[partintro]]
|
||||
--
|
||||
This section goes into how to develop a Spring Batch `Job` with a single `Step` by using the
|
||||
starter included in Spring Cloud Task. This starter lets you use configuration
|
||||
to define an `ItemReader`, an `ItemWriter`, or a full single-step Spring Batch `Job`.
|
||||
For more about Spring Batch and its capabilities, see the
|
||||
https://spring.io/projects/spring-batch[Spring Batch documentation].
|
||||
--
|
||||
|
||||
To obtain the starter for Maven, add the following to your build:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-single-step-batch-job</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
----
|
||||
====
|
||||
|
||||
To obtain the starter for Gradle, add the following to your build:
|
||||
|
||||
====
|
||||
[source,groovy]
|
||||
----
|
||||
compile "org.springframework.cloud:spring-cloud-starter-single-step-batch-job:2.3.0"
|
||||
----
|
||||
====
|
||||
|
||||
[[job-definition]]
|
||||
== Defining a Job
|
||||
|
||||
You can use the starter to define as little as an `ItemReader` or an `ItemWriter` or as much as a full `Job`.
|
||||
In this section, we define which properties are required to be defined to configure a
|
||||
`Job`.
|
||||
|
||||
[[job-definition-properties]]
|
||||
=== Properties
|
||||
|
||||
To begin, the starter provides a set of properties that let you configure the basics of a Job with one Step:
|
||||
|
||||
.Job Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.jobName`
|
||||
| `String`
|
||||
| `null`
|
||||
| The name of the job.
|
||||
|
||||
| `spring.batch.job.stepName`
|
||||
| `String`
|
||||
| `null`
|
||||
| The name of the step.
|
||||
|
||||
| `spring.batch.job.chunkSize`
|
||||
| `Integer`
|
||||
| `null`
|
||||
| The number of items to be processed per transaction.
|
||||
|===
|
||||
|
||||
With the above properties configured, you have a job with a single, chunk-based step.
|
||||
This chunk-based step reads, processes, and writes `Map<String, Object>` instances as the
|
||||
items. However, the step does not yet do anything. You need to configure an `ItemReader`, an
|
||||
optional `ItemProcessor`, and an `ItemWriter` to give it something to do. To configure one
|
||||
of these, you can either use properties and configure one of the options that has provided
|
||||
autoconfiguration or you can configure your own with the standard Spring configuration
|
||||
mechanisms.
|
||||
|
||||
NOTE: If you configure your own, the input and output types must match the others in the step.
|
||||
The `ItemReader` implementations and `ItemWriter` implementations in this starter all use
|
||||
a `Map<String, Object>` as the input and the output item.
|
||||
|
||||
[[item-readers]]
|
||||
== Autoconfiguration for ItemReader Implementations
|
||||
|
||||
This starter provides autoconfiguration for four different `ItemReader` implementations:
|
||||
`AmqpItemReader`, `FlatFileItemReader`, `JdbcCursorItemReader`, and `KafkaItemReader`.
|
||||
In this section, we outline how to configure each of these by using the provided
|
||||
autoconfiguration.
|
||||
|
||||
[[amqpitemreader]]
|
||||
=== AmqpItemReader
|
||||
|
||||
You can read from a queue or topic with AMQP by using the `AmqpItemReader`. The
|
||||
autoconfiguration for this `ItemReader` implementation is dependent upon two sets of
|
||||
configuration. The first is the configuration of an `AmqpTemplate`. You can either
|
||||
configure this yourself or use the autoconfiguration provided by Spring Boot. See the
|
||||
https://docs.spring.io/spring-boot/docs/3.0.x/reference/htmlsingle/#messaging.amqp.rabbitmq[Spring Boot AMQP documentation].
|
||||
Once you have configured the `AmqpTemplate`, you can enable the batch capabilities to support it
|
||||
by setting the following properties:
|
||||
|
||||
.`AmqpItemReader` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.amqpitemreader.enabled`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| If `true`, the autoconfiguration will execute.
|
||||
|
||||
| `spring.batch.job.amqpitemreader.jsonConverterEnabled`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Indicates if the `Jackson2JsonMessageConverter` should be registered to parse messages.
|
||||
|===
|
||||
|
||||
For more information, see the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/amqp/AmqpItemReader.html[`AmqpItemReader` documentation].
|
||||
|
||||
[[flatfileitemreader]]
|
||||
=== FlatFileItemReader
|
||||
|
||||
`FlatFileItemReader` lets you read from flat files (such as CSVs
|
||||
and other file formats). To read from a file, you can provide some components
|
||||
yourself through normal Spring configuration (`LineTokenizer`, `RecordSeparatorPolicy`,
|
||||
`FieldSetMapper`, `LineMapper`, or `SkippedLinesCallback`). You can also use the
|
||||
following properties to configure the reader:
|
||||
|
||||
.`FlatFileItemReader` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.saveState`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Determines if the state should be saved for restarts.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Name used to provide unique keys in the `ExecutionContext`.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.maxItemcount`
|
||||
| `int`
|
||||
| `Integer.MAX_VALUE`
|
||||
| Maximum number of items to be read from the file.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.currentItemCount`
|
||||
| `int`
|
||||
| 0
|
||||
| Number of items that have already been read. Used on restarts.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.comments`
|
||||
| `List<String>`
|
||||
| empty List
|
||||
| A list of Strings that indicate commented lines (lines to be ignored) in the file.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.resource`
|
||||
| `Resource`
|
||||
| `null`
|
||||
| The resource to be read.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.strict`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| If set to `true`, the reader throws an exception if the resource is not found.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.encoding`
|
||||
| `String`
|
||||
| `FlatFileItemReader.DEFAULT_CHARSET`
|
||||
| Encoding to be used when reading the file.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.linesToSkip`
|
||||
| `int`
|
||||
| 0
|
||||
| Indicates the number of lines to skip at the start of a file.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.delimited`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether the file is a delimited file (CSV and other formats). Only one of this property or `spring.batch.job.flatfileitemreader.fixedLength` can be `true` at the same time.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.delimiter`
|
||||
| `String`
|
||||
| `DelimitedLineTokenizer.DELIMITER_COMMA`
|
||||
| If reading a delimited file, indicates the delimiter to parse on.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.quoteCharacter`
|
||||
| `char`
|
||||
| `DelimitedLineTokenizer.DEFAULT_QUOTE_CHARACTER`
|
||||
| Used to determine the character used to quote values.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.includedFields`
|
||||
| `List<Integer>`
|
||||
| empty list
|
||||
| A list of indices to determine which fields in a record to include in the item.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.fixedLength`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates if a file's records are parsed by column numbers. Only one of this property or `spring.batch.job.flatfileitemreader.delimited` can be `true` at the same time.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.ranges`
|
||||
| `List<Range>`
|
||||
| empty list
|
||||
| List of column ranges by which to parse a fixed width record. See the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/file/transform/Range.html[Range documentation].
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.names`
|
||||
| `String []`
|
||||
| `null`
|
||||
| List of names for each field parsed from a record. These names are the keys in the `Map<String, Object>` in the items returned from this `ItemReader`.
|
||||
|
||||
| `spring.batch.job.flatfileitemreader.parsingStrict`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| If set to `true`, the mapping fails if the fields cannot be mapped.
|
||||
|===
|
||||
|
||||
See the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/file/FlatFileItemReader.html[`FlatFileItemReader` documentation].
|
||||
|
||||
[[jdbcCursorItemReader]]
|
||||
=== JdbcCursorItemReader
|
||||
|
||||
The `JdbcCursorItemReader` runs a query against a relational database and iterates over
|
||||
the resulting cursor (`ResultSet`) to provide the resulting items. This autoconfiguration
|
||||
lets you provide a `PreparedStatementSetter`, a `RowMapper`, or both. You
|
||||
can also use the following properties to configure a `JdbcCursorItemReader`:
|
||||
|
||||
.`JdbcCursorItemReader` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.saveState`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Determines whether the state should be saved for restarts.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Name used to provide unique keys in the `ExecutionContext`.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.maxItemcount`
|
||||
| `int`
|
||||
| `Integer.MAX_VALUE`
|
||||
| Maximum number of items to be read from the file.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.currentItemCount`
|
||||
| `int`
|
||||
| 0
|
||||
| Number of items that have already been read. Used on restarts.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.fetchSize`
|
||||
| `int`
|
||||
|
|
||||
| A hint to the driver to indicate how many records to retrieve per call to the database system. For best performance, you usually want to set it to match the chunk size.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.maxRows`
|
||||
| `int`
|
||||
|
|
||||
| Maximum number of items to read from the database.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.queryTimeout`
|
||||
| `int`
|
||||
|
|
||||
| Number of milliseconds for the query to timeout.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.ignoreWarnings`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Determines whether the reader should ignore SQL warnings when processing.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.verifyCursorPosition`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Indicates whether the cursor's position should be verified after each read to verify that the `RowMapper` did not advance the cursor.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.driverSupportsAbsolute`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether the driver supports absolute positioning of a cursor.
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.useSharedExtendedConnection`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether the connection is shared with other processing (and is therefore part of a transaction).
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.sql`
|
||||
| `String`
|
||||
| `null`
|
||||
| SQL query from which to read.
|
||||
|===
|
||||
|
||||
You can also specify JDBC DataSource specifically for the reader by using the following properties:
|
||||
.`JdbcCursorItemReader` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.jdbccursoritemreader.datasource.enable`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Determines whether `JdbcCursorItemReader` `DataSource` should be enabled.
|
||||
|
||||
| `jdbccursoritemreader.datasource.url`
|
||||
| `String`
|
||||
| `null`
|
||||
| JDBC URL of the database.
|
||||
|
||||
| `jdbccursoritemreader.datasource.username`
|
||||
| `String`
|
||||
| `null`
|
||||
| Login username of the database.
|
||||
|
||||
| `jdbccursoritemreader.datasource.password`
|
||||
| `String`
|
||||
| `null`
|
||||
| Login password of the database.
|
||||
|
||||
| `jdbccursoritemreader.datasource.driver-class-name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Fully qualified name of the JDBC driver.
|
||||
|===
|
||||
|
||||
NOTE: The default `DataSource` will be used by the `JDBCCursorItemReader` if the `jdbccursoritemreader_datasource` is not specified.
|
||||
|
||||
See the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/database/JdbcCursorItemReader.html[`JdbcCursorItemReader` documentation].
|
||||
|
||||
[[kafkaItemReader]]
|
||||
=== KafkaItemReader
|
||||
|
||||
Ingesting a partition of data from a Kafka topic is useful and exactly what the
|
||||
`KafkaItemReader` can do. To configure a `KafkaItemReader`, two pieces
|
||||
of configuration are required. First, configuring Kafka with Spring Boot's Kafka
|
||||
autoconfiguration is required (see the
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.kafka.additional-properties[Spring Boot Kafka documentation]).
|
||||
Once you have configured the Kafka properties from Spring Boot, you can configure the `KafkaItemReader`
|
||||
itself by setting the following properties:
|
||||
|
||||
.`KafkaItemReader` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.kafkaitemreader.name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Name used to provide unique keys in the `ExecutionContext`.
|
||||
|
||||
| `spring.batch.job.kafkaitemreader.topic`
|
||||
| `String`
|
||||
| `null`
|
||||
| Name of the topic from which to read.
|
||||
|
||||
| `spring.batch.job.kafkaitemreader.partitions`
|
||||
| `List<Integer>`
|
||||
| empty list
|
||||
| List of partition indices from which to read.
|
||||
|
||||
| `spring.batch.job.kafkaitemreader.pollTimeOutInSeconds`
|
||||
| `long`
|
||||
| 30
|
||||
| Timeout for the `poll()` operations.
|
||||
|
||||
| `spring.batch.job.kafkaitemreader.saveState`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Determines whether the state should be saved for restarts.
|
||||
|===
|
||||
|
||||
See the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/kafka/KafkaItemReader.html[`KafkaItemReader` documentation].
|
||||
|
||||
[[nativeCompilation]]
|
||||
=== Native Compilation
|
||||
The advantage of Single Step Batch Processing is that it lets you dynamically select which reader and writer beans to use at runtime when you use the JVM.
|
||||
However, when you use native compilation, you must determine the reader and writer at build time instead of runtime.
|
||||
The following example does so:
|
||||
|
||||
[source,xml]
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>process-aot</id>
|
||||
<goals>
|
||||
<goal>process-aot</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<jvmArguments>
|
||||
-Dspring.batch.job.flatfileitemreader.name=fooReader
|
||||
-Dspring.batch.job.flatfileitemwriter.name=fooWriter
|
||||
</jvmArguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
[[item-processors]]
|
||||
== ItemProcessor Configuration
|
||||
|
||||
The single-step batch job autoconfiguration accepts an `ItemProcessor` if one
|
||||
is available within the `ApplicationContext`. If one is found of the correct type
|
||||
(`ItemProcessor<Map<String, Object>, Map<String, Object>>`), it is autowired
|
||||
into the step.
|
||||
|
||||
[[item-writers]]
|
||||
== Autoconfiguration for ItemWriter implementations
|
||||
|
||||
This starter provides autoconfiguration for `ItemWriter` implementations that
|
||||
match the supported `ItemReader` implementations: `AmqpItemWriter`,
|
||||
`FlatFileItemWriter`, `JdbcItemWriter`, and `KafkaItemWriter`. This section
|
||||
covers how to use autoconfiguration to configure a supported `ItemWriter`.
|
||||
|
||||
[[amqpitemwriter]]
|
||||
=== AmqpItemWriter
|
||||
|
||||
To write to a RabbitMQ queue, you need two sets of configuration. First, you need an
|
||||
`AmqpTemplate`. The easiest way to get this is by using Spring Boot's
|
||||
RabbitMQ autoconfiguration. See the https://docs.spring.io/spring-boot/docs/3.0.x/reference/htmlsingle/#messaging.amqp.rabbitmq[Spring Boot AMQP documentation].
|
||||
|
||||
Once you have configured the `AmqpTemplate`, you can configure the `AmqpItemWriter` by setting the
|
||||
following properties:
|
||||
|
||||
.`AmqpItemWriter` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.amqpitemwriter.enabled`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| If `true`, the autoconfiguration runs.
|
||||
|
||||
| `spring.batch.job.amqpitemwriter.jsonConverterEnabled`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Indicates whether `Jackson2JsonMessageConverter` should be registered to convert messages.
|
||||
|===
|
||||
|
||||
[[flatfileitemwriter]]
|
||||
=== FlatFileItemWriter
|
||||
|
||||
To write a file as the output of the step, you can configure `FlatFileItemWriter`.
|
||||
Autoconfiguration accepts components that have been explicitly configured (such as `LineAggregator`,
|
||||
`FieldExtractor`, `FlatFileHeaderCallback`, or a `FlatFileFooterCallback`) and
|
||||
components that have been configured by setting the following properties specified:
|
||||
|
||||
.`FlatFileItemWriter` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.resource`
|
||||
| `Resource`
|
||||
| `null`
|
||||
| The resource to be read.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.delimited`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether the output file is a delimited file. If `true`, `spring.batch.job.flatfileitemwriter.formatted` must be `false`.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.formatted`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether the output file a formatted file. If `true`, `spring.batch.job.flatfileitemwriter.delimited` must be `false`.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.format`
|
||||
| `String`
|
||||
| `null`
|
||||
| The format used to generate the output for a formatted file. The formatting is performed by using `String.format`.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.locale`
|
||||
| `Locale`
|
||||
| `Locale.getDefault()`
|
||||
| The `Locale` to be used when generating the file.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.maximumLength`
|
||||
| `int`
|
||||
| 0
|
||||
| Max length of the record. If 0, the size is unbounded.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.minimumLength`
|
||||
| `int`
|
||||
| 0
|
||||
| The minimum record length.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.delimiter`
|
||||
| `String`
|
||||
| `,`
|
||||
| The `String` used to delimit fields in a delimited file.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.encoding`
|
||||
| `String`
|
||||
| `FlatFileItemReader.DEFAULT_CHARSET`
|
||||
| Encoding to use when writing the file.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.forceSync`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether a file should be force-synced to the disk on flush.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.names`
|
||||
| `String []`
|
||||
| `null`
|
||||
| List of names for each field parsed from a record. These names are the keys in the `Map<String, Object>` for the items received by this `ItemWriter`.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.append`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Indicates whether a file should be appended to if the output file is found.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.lineSeparator`
|
||||
| `String`
|
||||
| `FlatFileItemWriter.DEFAULT_LINE_SEPARATOR`
|
||||
| What `String` to use to separate lines in the output file.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Name used to provide unique keys in the `ExecutionContext`.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.saveState`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Determines whether the state should be saved for restarts.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.shouldDeleteIfEmpty`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| If set to `true`, an empty file (there is no output) is deleted when the job completes.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.shouldDeleteIfExists`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| If set to `true` and a file is found where the output file should be, it is deleted before the step begins.
|
||||
|
||||
| `spring.batch.job.flatfileitemwriter.transactional`
|
||||
| `boolean`
|
||||
| `FlatFileItemWriter.DEFAULT_TRANSACTIONAL`
|
||||
| Indicates whether the reader is a transactional queue (indicating that the items read are returned to the queue upon a failure).
|
||||
|===
|
||||
|
||||
See the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/file/FlatFileItemWriter.html[`FlatFileItemWriter` documentation].
|
||||
|
||||
[[jdbcitemwriter]]
|
||||
=== JdbcBatchItemWriter
|
||||
|
||||
To write the output of a step to a relational database, this starter provides the ability
|
||||
to autoconfigure a `JdbcBatchItemWriter`. The autoconfiguration lets you provide your
|
||||
own `ItemPreparedStatementSetter` or `ItemSqlParameterSourceProvider` and
|
||||
configuration options by setting the following properties:
|
||||
|
||||
.`JdbcBatchItemWriter` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.jdbcbatchitemwriter.name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Name used to provide unique keys in the `ExecutionContext`.
|
||||
|
||||
| `spring.batch.job.jdbcbatchitemwriter.sql`
|
||||
| `String`
|
||||
| `null`
|
||||
| The SQL used to insert each item.
|
||||
|
||||
| `spring.batch.job.jdbcbatchitemwriter.assertUpdates`
|
||||
| `boolean`
|
||||
| `true`
|
||||
| Whether to verify that every insert results in the update of at least one record.
|
||||
|===
|
||||
|
||||
You can also specify JDBC DataSource specifically for the writer by using the following properties:
|
||||
.`JdbcBatchItemWriter` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.jdbcbatchitemwriter.datasource.enable`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Determines whether `JdbcCursorItemReader` `DataSource` should be enabled.
|
||||
|
||||
| `jdbcbatchitemwriter.datasource.url`
|
||||
| `String`
|
||||
| `null`
|
||||
| JDBC URL of the database.
|
||||
|
||||
| `jdbcbatchitemwriter.datasource.username`
|
||||
| `String`
|
||||
| `null`
|
||||
| Login username of the database.
|
||||
|
||||
| `jdbcbatchitemwriter.datasource.password`
|
||||
| `String`
|
||||
| `null`
|
||||
| Login password of the database.
|
||||
|
||||
| `jdbcbatchitemreader.datasource.driver-class-name`
|
||||
| `String`
|
||||
| `null`
|
||||
| Fully qualified name of the JDBC driver.
|
||||
|===
|
||||
|
||||
NOTE: The default `DataSource` will be used by the `JdbcBatchItemWriter` if the `jdbcbatchitemwriter_datasource` is not specified.
|
||||
|
||||
See the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/database/JdbcBatchItemWriter.html[`JdbcBatchItemWriter` documentation].
|
||||
|
||||
[[kafkaitemwriter]]
|
||||
=== KafkaItemWriter
|
||||
|
||||
To write step output to a Kafka topic, you need `KafkaItemWriter`. This starter
|
||||
provides autoconfiguration for a `KafkaItemWriter` by using facilities from two places.
|
||||
The first is Spring Boot's Kafka autoconfiguration. (See the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.kafka.additional-properties[Spring Boot Kafka documentation].)
|
||||
Second, this starter lets you configure two properties on the writer.
|
||||
|
||||
.`KafkaItemWriter` Properties
|
||||
|===
|
||||
| Property | Type | Default Value | Description
|
||||
|
||||
| `spring.batch.job.kafkaitemwriter.topic`
|
||||
| `String`
|
||||
| `null`
|
||||
| The Kafka topic to which to write.
|
||||
|
||||
| `spring.batch.job.kafkaitemwriter.delete`
|
||||
| `boolean`
|
||||
| `false`
|
||||
| Whether the items being passed to the writer are all to be sent as delete events to the topic.
|
||||
|===
|
||||
|
||||
For more about the configuration options for the `KafkaItemWriter`, see the https://docs.spring.io/spring-batch/docs/4.3.x/api/org/springframework/batch/item/kafka/KafkaItemWriter.html[`KafkaItemWiter` documentation].
|
||||
|
||||
[[spring-aot]]
|
||||
=== Spring AOT
|
||||
When using Spring AOT with Single Step Batch Starter you must set the reader and
|
||||
writer name properties at compile time (unless you create a bean(s) for the reader and or writer).
|
||||
To do this you must include the name of the reader and writer that you wish to use as
|
||||
and argument or environment variable in the boot maven plugin or gradle plugin. For example if
|
||||
you wish to enable the `FlatFileItemReader` and `FlatFileItemWriter` in Maven it would look like:
|
||||
|
||||
```
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>process-aot</id>
|
||||
<goals>
|
||||
<goal>process-aot</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<arguments>
|
||||
<argument>--spring.batch.job.flatfileitemreader.name=foobar</argument>
|
||||
<argument>--spring.batch.job.flatfileitemwriter.name=fooWriter</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
```
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
[[batch]]
|
||||
= Batch
|
||||
|
||||
[[partintro]]
|
||||
--
|
||||
This section goes into more detail about Spring Cloud Task's integration with Spring
|
||||
Batch. Tracking the association between a job execution and the task in which it was
|
||||
executed as well as remote partitioning through Spring Cloud Deployer are covered in
|
||||
this section.
|
||||
--
|
||||
|
||||
[[batch-association]]
|
||||
== Associating a Job Execution to the Task in which It Was Executed
|
||||
|
||||
Spring Boot provides facilities for the execution of batch jobs within an über-jar.
|
||||
Spring Boot's support of this functionality lets a developer execute multiple batch jobs
|
||||
within that execution. Spring Cloud Task provides the ability to associate the execution
|
||||
of a job (a job execution) with a task's execution so that one can be traced back to the
|
||||
other.
|
||||
|
||||
Spring Cloud Task achieves this functionality by using the `TaskBatchExecutionListener`.
|
||||
By default,
|
||||
this listener is auto configured in any context that has both a Spring Batch Job
|
||||
configured (by having a bean of type `Job` defined in the context) and the
|
||||
`spring-cloud-task-batch` jar on the classpath. The listener is injected into all jobs
|
||||
that meet those conditions.
|
||||
|
||||
[[batch-association-override]]
|
||||
=== Overriding the TaskBatchExecutionListener
|
||||
|
||||
To prevent the listener from being injected into any batch jobs within the current
|
||||
context, you can disable the autoconfiguration by using standard Spring Boot mechanisms.
|
||||
|
||||
To only have the listener injected into particular jobs within the context, override the
|
||||
`batchTaskExecutionListenerBeanPostProcessor` and provide a list of job bean IDs, as shown
|
||||
in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public static TaskBatchExecutionListenerBeanPostProcessor batchTaskExecutionListenerBeanPostProcessor() {
|
||||
TaskBatchExecutionListenerBeanPostProcessor postProcessor =
|
||||
new TaskBatchExecutionListenerBeanPostProcessor();
|
||||
|
||||
postProcessor.setJobNames(Arrays.asList(new String[] {"job1", "job2"}));
|
||||
|
||||
return postProcessor;
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: You can find a sample batch application in the samples module of the Spring Cloud
|
||||
Task Project,
|
||||
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-samples/batch-job[here].
|
||||
|
||||
|
||||
[[batch-partitioning]]
|
||||
== Remote Partitioning
|
||||
|
||||
Spring Cloud Deployer provides facilities for launching Spring Boot-based applications on
|
||||
most cloud infrastructures. The `DeployerPartitionHandler` and
|
||||
`DeployerStepExecutionHandler` delegate the launching of worker step executions to Spring
|
||||
Cloud Deployer.
|
||||
|
||||
To configure the `DeployerStepExecutionHandler`, you must provide a `Resource`
|
||||
representing the Spring Boot über-jar to be executed, a `TaskLauncherHandler`, and a
|
||||
`JobExplorer`. You can configure any environment properties as well as the max number of
|
||||
workers to be executing at once, the interval to poll for the results (defaults to 10
|
||||
seconds), and a timeout (defaults to -1 or no timeout). The following example shows how
|
||||
configuring this `PartitionHandler` might look:
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public PartitionHandler partitionHandler(TaskLauncher taskLauncher,
|
||||
JobExplorer jobExplorer) throws Exception {
|
||||
|
||||
MavenProperties mavenProperties = new MavenProperties();
|
||||
mavenProperties.setRemoteRepositories(new HashMap<>(Collections.singletonMap("springRepo",
|
||||
new MavenProperties.RemoteRepository(repository))));
|
||||
|
||||
Resource resource =
|
||||
MavenResource.parse(String.format("%s:%s:%s",
|
||||
"io.spring.cloud",
|
||||
"partitioned-batch-job",
|
||||
"1.1.0.RELEASE"), mavenProperties);
|
||||
|
||||
DeployerPartitionHandler partitionHandler =
|
||||
new DeployerPartitionHandler(taskLauncher, jobExplorer, resource, "workerStep");
|
||||
|
||||
List<String> commandLineArgs = new ArrayList<>(3);
|
||||
commandLineArgs.add("--spring.profiles.active=worker");
|
||||
commandLineArgs.add("--spring.cloud.task.initialize.enable=false");
|
||||
commandLineArgs.add("--spring.batch.initializer.enabled=false");
|
||||
|
||||
partitionHandler.setCommandLineArgsProvider(
|
||||
new PassThroughCommandLineArgsProvider(commandLineArgs));
|
||||
partitionHandler.setEnvironmentVariablesProvider(new NoOpEnvironmentVariablesProvider());
|
||||
partitionHandler.setMaxWorkers(2);
|
||||
partitionHandler.setApplicationName("PartitionedBatchJobTask");
|
||||
|
||||
return partitionHandler;
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: When passing environment variables to partitions, each partition may
|
||||
be on a different machine with different environment settings.
|
||||
Consequently, you should pass only those environment variables that are required.
|
||||
|
||||
Notice in the example above that we have set the maximum number of workers to 2.
|
||||
Setting the maximum of workers establishes the maximum number of
|
||||
partitions that should be running at one time.
|
||||
|
||||
The `Resource` to be executed is expected to be a Spring Boot über-jar with a
|
||||
`DeployerStepExecutionHandler` configured as a `CommandLineRunner` in the current context.
|
||||
The repository enumerated in the preceding example should be the remote repository in
|
||||
which the über-jar is located. Both the manager and worker are expected to have visibility
|
||||
into the same data store being used as the job repository and task repository. Once the
|
||||
underlying infrastructure has bootstrapped the Spring Boot jar and Spring Boot has
|
||||
launched the `DeployerStepExecutionHandler`, the step handler executes the requested
|
||||
`Step`. The following example shows how to configure the `DeployerStepExecutionHandler`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public DeployerStepExecutionHandler stepExecutionHandler(JobExplorer jobExplorer) {
|
||||
DeployerStepExecutionHandler handler =
|
||||
new DeployerStepExecutionHandler(this.context, jobExplorer, this.jobRepository);
|
||||
|
||||
return handler;
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: You can find a sample remote partition application in the samples module of the
|
||||
Spring Cloud Task project,
|
||||
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-samples/partitioned-batch-job[here].
|
||||
|
||||
[[asynchronously-launch-remote-batch-partitions]]
|
||||
=== Asynchronously launch remote batch partitions
|
||||
|
||||
By default batch partitions are launched sequentially. However, in some cases this may affect performance as each launch will block until the resource (For example: provisioning a pod in Kubernetes) is provisioned.
|
||||
In these cases you can provide a `ThreadPoolTaskExecutor` to the `DeployerPartitionHandler`. This will launch the remote batch partitions based on the configuration of the `ThreadPoolTaskExecutor`.
|
||||
For example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setThreadNamePrefix("default_task_executor_thread");
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PartitionHandler partitionHandler(TaskLauncher taskLauncher, JobExplorer jobExplorer,
|
||||
TaskRepository taskRepository, ThreadPoolTaskExecutor executor) throws Exception {
|
||||
Resource resource = this.resourceLoader
|
||||
.getResource("maven://io.spring.cloud:partitioned-batch-job:2.2.0.BUILD-SNAPSHOT");
|
||||
|
||||
DeployerPartitionHandler partitionHandler =
|
||||
new DeployerPartitionHandler(taskLauncher, jobExplorer, resource,
|
||||
"workerStep", taskRepository, executor);
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: We need to close the context since the use of `ThreadPoolTaskExecutor` leaves a thread active thus the app will not terminate. To close the application appropriately, we will need to set `spring.cloud.task.closecontextEnabled` property to `true`.
|
||||
|
||||
|
||||
[[notes-on-developing-a-batch-partitioned-application-for-the-kubernetes-platform]]
|
||||
=== Notes on Developing a Batch-partitioned application for the Kubernetes Platform
|
||||
|
||||
* When deploying partitioned apps on the Kubernetes platform, you must use the following
|
||||
dependency for the Spring Cloud Kubernetes Deployer:
|
||||
+
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-deployer-kubernetes</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
* The application name for the task application and its partitions need to follow
|
||||
the following regex pattern: `[a-z0-9]([-a-z0-9]*[a-z0-9])`.
|
||||
Otherwise, an exception is thrown.
|
||||
|
||||
|
||||
[[batch-informational-messages]]
|
||||
== Batch Informational Messages
|
||||
|
||||
Spring Cloud Task provides the ability for batch jobs to emit informational messages. The
|
||||
"`xref:stream.adoc#stream-integration-batch-events[Spring Batch Events]`" section covers this feature in detail.
|
||||
|
||||
[[batch-failures-and-tasks]]
|
||||
== Batch Job Exit Codes
|
||||
|
||||
As discussed xref:features.adoc#features-lifecycle-exit-codes[earlier], Spring Cloud Task
|
||||
applications support the ability to record the exit code of a task execution. However, in
|
||||
cases where you run a Spring Batch Job within a task, regardless of how the Batch Job
|
||||
Execution completes, the result of the task is always zero when using the default
|
||||
Batch/Boot behavior. Keep in mind that a task is a boot application and that the exit code
|
||||
returned from the task is the same as a boot application.
|
||||
To override this behavior and allow the task to return an exit code other than zero when a
|
||||
batch job returns an
|
||||
https://docs.spring.io/spring-batch/4.0.x/reference/html/step.html#batchStatusVsExitStatus[BatchStatus]
|
||||
of `FAILED`, set `spring.cloud.task.batch.fail-on-job-failure` to `true`. Then the exit code
|
||||
can be 1 (the default) or be based on the
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-exit[specified
|
||||
`ExitCodeGenerator`])
|
||||
|
||||
This functionality uses a new `ApplicationRunner` that replaces the one provided by Spring
|
||||
Boot. By default, it is configured with the same order. However, if you want to customize
|
||||
the order in which the `ApplicationRunner` is run, you can set its order by setting the
|
||||
`spring.cloud.task.batch.applicationRunnerOrder` property. To have your task return the
|
||||
exit code based on the result of the batch job execution, you need to write your own
|
||||
`CommandLineRunner`.
|
||||
//TODO Great place for a example showing how a custom CommandLineRunner
|
||||
@@ -1,454 +0,0 @@
|
||||
|
||||
[[features]]
|
||||
= Features
|
||||
|
||||
[[partintro]]
|
||||
--
|
||||
This section goes into more detail about Spring Cloud Task, including how to use it, how
|
||||
to configure it, and the appropriate extension points.
|
||||
--
|
||||
|
||||
[[features-lifecycle]]
|
||||
== The lifecycle of a Spring Cloud Task
|
||||
|
||||
In most cases, the modern cloud environment is designed around the execution of processes
|
||||
that are not expected to end. If they do end, they are typically restarted. While most
|
||||
platforms do have some way to run a process that is not restarted when it ends, the
|
||||
results of that run are typically not maintained in a consumable way. Spring Cloud
|
||||
Task offers the ability to execute short-lived processes in an environment and record the
|
||||
results. Doing so allows for a microservices architecture around short-lived processes as
|
||||
well as longer running services through the integration of tasks by messages.
|
||||
|
||||
While this functionality is useful in a cloud environment, the same issues can arise in a
|
||||
traditional deployment model as well. When running Spring Boot applications with a
|
||||
scheduler such as cron, it can be useful to be able to monitor the results of the
|
||||
application after its completion.
|
||||
|
||||
Spring Cloud Task takes the approach that a Spring Boot application can have a start and
|
||||
an end and still be successful. Batch applications are one example of how processes that
|
||||
are expected to end (and that are often short-lived) can be helpful.
|
||||
|
||||
Spring Cloud Task records the lifecycle events of a given task. Most long-running
|
||||
processes, typified by most web applications, do not save their lifecycle events. The
|
||||
tasks at the heart of Spring Cloud Task do.
|
||||
|
||||
The lifecycle consists of a single task execution. This is a physical execution of a
|
||||
Spring Boot application configured to be a task (that is, it has the Sprint Cloud Task dependencies).
|
||||
|
||||
At the beginning of a task, before any `CommandLineRunner` or `ApplicationRunner`
|
||||
implementations have been run, an entry in the `TaskRepository` that records the start
|
||||
event is created. This event is triggered through `SmartLifecycle#start` being triggered
|
||||
by the Spring Framework. This indicates to the system that all beans are ready for use and
|
||||
comes before running any of the `CommandLineRunner` or `ApplicationRunner` implementations
|
||||
provided by Spring Boot.
|
||||
|
||||
NOTE: The recording of a task only occurs upon the successful bootstrapping of an
|
||||
`ApplicationContext`. If the context fails to bootstrap at all, the task's run is not
|
||||
recorded.
|
||||
|
||||
Upon completion of all of the `*Runner#run` calls from Spring Boot or the failure of an
|
||||
`ApplicationContext` (indicated by an `ApplicationFailedEvent`), the task execution is
|
||||
updated in the repository with the results.
|
||||
|
||||
NOTE: If the application requires the `ApplicationContext` to be closed at the
|
||||
completion of a task (all `*Runner#run` methods have been called and the task
|
||||
repository has been updated), set the property `spring.cloud.task.closecontextEnabled`
|
||||
to true.
|
||||
|
||||
[[features-task-execution-details]]
|
||||
=== The TaskExecution
|
||||
|
||||
The information stored in the `TaskRepository` is modeled in the `TaskExecution` class and
|
||||
consists of the following information:
|
||||
|
||||
|===
|
||||
|Field |Description
|
||||
|
||||
|`executionid`
|
||||
|The unique ID for the task's run.
|
||||
|
||||
|`exitCode`
|
||||
|The exit code generated from an `ExitCodeExceptionMapper` implementation. If there is no
|
||||
exit code generated but an `ApplicationFailedEvent` is thrown, 1 is set. Otherwise, it is
|
||||
assumed to be 0.
|
||||
|
||||
|`taskName`
|
||||
|The name for the task, as determined by the configured `TaskNameResolver`.
|
||||
|
||||
|`startTime`
|
||||
|The time the task was started, as indicated by the `SmartLifecycle#start` call.
|
||||
|
||||
|`endTime`
|
||||
|The time the task was completed, as indicated by the `ApplicationReadyEvent`.
|
||||
|
||||
|`exitMessage`
|
||||
|Any information available at the time of exit. This can programmatically be set by a
|
||||
`TaskExecutionListener`.
|
||||
|
||||
|`errorMessage`
|
||||
|If an exception is the cause of the end of the task (as indicated by an
|
||||
`ApplicationFailedEvent`), the stack trace for that exception is stored here.
|
||||
|
||||
|`arguments`
|
||||
|A `List` of the string command line arguments as they were passed into the executable
|
||||
boot application.
|
||||
|===
|
||||
|
||||
[[features-lifecycle-exit-codes]]
|
||||
=== Mapping Exit Codes
|
||||
|
||||
When a task completes, it tries to return an exit code to the OS. If we take a look
|
||||
at our xref:getting-started.adoc#getting-started-developing-first-task[original example], we can see that we are
|
||||
not controlling that aspect of our application. So, if an exception is thrown, the JVM
|
||||
returns a code that may or may not be of any use to you in debugging.
|
||||
|
||||
Consequently, Spring Boot provides an interface, `ExitCodeExceptionMapper`, that lets you
|
||||
map uncaught exceptions to exit codes. Doing so lets you indicate, at the level of exit
|
||||
codes, what went wrong. Also, by mapping exit codes in this manner, Spring Cloud Task
|
||||
records the returned exit code.
|
||||
|
||||
If the task terminates with a SIG-INT or a SIG-TERM, the exit code is zero unless
|
||||
otherwise specified within the code.
|
||||
|
||||
NOTE: While the task is running, the exit code is stored as a null in the repository.
|
||||
Once the task completes, the appropriate exit code is stored based on the guidelines described
|
||||
earlier in this section.
|
||||
|
||||
[[features-configuration]]
|
||||
== Configuration
|
||||
|
||||
Spring Cloud Task provides a ready-to-use configuration, as defined in the
|
||||
`DefaultTaskConfigurer` and `SimpleTaskConfiguration` classes. This section walks through
|
||||
the defaults and how to customize Spring Cloud Task for your needs.
|
||||
|
||||
[[features-data-source]]
|
||||
=== DataSource
|
||||
|
||||
Spring Cloud Task uses a datasource for storing the results of task executions. By
|
||||
default, we provide an in-memory instance of H2 to provide a simple method of
|
||||
bootstrapping development. However, in a production environment, you probably want to
|
||||
configure your own `DataSource`.
|
||||
|
||||
If your application uses only a single `DataSource` and that serves as both your business
|
||||
schema and the task repository, all you need to do is provide any `DataSource` (the
|
||||
easiest way to do so is through Spring Boot's configuration conventions). This
|
||||
`DataSource` is automatically used by Spring Cloud Task for the repository.
|
||||
|
||||
If your application uses more than one `DataSource`, you need to configure the task
|
||||
repository with the appropriate `DataSource`. This customization can be done through an
|
||||
implementation of `TaskConfigurer`.
|
||||
|
||||
[[features-table-prefix]]
|
||||
=== Table Prefix
|
||||
One modifiable property of `TaskRepository` is the table prefix for the task tables. By
|
||||
default, they are all prefaced with `TASK_`. `TASK_EXECUTION` and `TASK_EXECUTION_PARAMS`
|
||||
are two examples. However, there are potential reasons to modify this prefix. If the
|
||||
schema name needs to be prepended to the table names or if more than one set of task
|
||||
tables is needed within the same schema, you must change the table prefix. You can do so
|
||||
by setting the `spring.cloud.task.tablePrefix` to the prefix you need, as follows:
|
||||
|
||||
`spring.cloud.task.tablePrefix=yourPrefix`
|
||||
|
||||
By using the `spring.cloud.task.tablePrefix`, a user assumes the responsibility to
|
||||
create the task tables that meet both the criteria for the task table schema but
|
||||
with modifications that are required for a user's business needs.
|
||||
You can utilize the Spring Cloud Task Schema DDL as a guide when creating your own Task DDL as seen
|
||||
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task[here].
|
||||
|
||||
|
||||
[[features-table-initialization]]
|
||||
=== Enable/Disable table initialization
|
||||
In cases where you are creating the task tables and do not wish for Spring Cloud Task to
|
||||
create them at task startup, set the `spring.cloud.task.initialize-enabled` property to
|
||||
`false`, as follows:
|
||||
|
||||
`spring.cloud.task.initialize-enabled=false`
|
||||
|
||||
It defaults to `true`.
|
||||
|
||||
NOTE: The property `spring.cloud.task.initialize.enable` has been deprecated.
|
||||
|
||||
[[features-generated_task_id]]
|
||||
=== Externally Generated Task ID
|
||||
|
||||
In some cases, you may want to allow for the time difference between when a task is
|
||||
requested and when the infrastructure actually launches it. Spring Cloud Task lets you
|
||||
create a `TaskExecution` when the task is requested. Then pass the execution ID of the
|
||||
generated `TaskExecution` to the task so that it can update the `TaskExecution` through
|
||||
the task's lifecycle.
|
||||
|
||||
A `TaskExecution` can be created by calling the `createTaskExecution` method on an
|
||||
implementation of the `TaskRepository` that references the datastore that holds
|
||||
the `TaskExecution` objects.
|
||||
|
||||
In order to configure your Task to use a generated `TaskExecutionId`, add the
|
||||
following property:
|
||||
|
||||
`spring.cloud.task.executionid=yourtaskId`
|
||||
|
||||
[[features-external_task_id]]
|
||||
=== External Task Id
|
||||
|
||||
Spring Cloud Task lets you store an external task ID for each
|
||||
`TaskExecution`. In order to configure your Task to use a generated `TaskExecutionId`, add the
|
||||
following property:
|
||||
|
||||
`spring.cloud.task.external-execution-id=<externalTaskId>`
|
||||
|
||||
[[features-parent_task_id]]
|
||||
=== Parent Task Id
|
||||
|
||||
Spring Cloud Task lets you store a parent task ID for each `TaskExecution`. An example of
|
||||
this would be a task that executes another task or tasks and you want to record which task
|
||||
launched each of the child tasks. In order to configure your Task to set a parent
|
||||
`TaskExecutionId` add the following property on the child task:
|
||||
|
||||
`spring.cloud.task.parent-execution-id=<parentExecutionTaskId>`
|
||||
|
||||
[[features-task-configurer]]
|
||||
=== TaskConfigurer
|
||||
|
||||
The `TaskConfigurer` is a strategy interface that lets you customize the way components of
|
||||
Spring Cloud Task are configured. By default, we provide the `DefaultTaskConfigurer` that
|
||||
provides logical defaults: `Map`-based in-memory components (useful for development if no
|
||||
`DataSource` is provided) and JDBC based components (useful if there is a `DataSource`
|
||||
available).
|
||||
|
||||
The `TaskConfigurer` lets you configure three main components:
|
||||
|
||||
|===
|
||||
|Component |Description |Default (provided by `DefaultTaskConfigurer`)
|
||||
|
||||
|`TaskRepository`
|
||||
|The implementation of the `TaskRepository` to be used.
|
||||
|`SimpleTaskRepository`
|
||||
|
||||
|`TaskExplorer`
|
||||
|The implementation of the `TaskExplorer` (a component for read-only access to the task
|
||||
repository) to be used.
|
||||
|`SimpleTaskExplorer`
|
||||
|
||||
|`PlatformTransactionManager`
|
||||
|A transaction manager to be used when running updates for tasks.
|
||||
|`JdbcTransactionManager` if a `DataSource` is used.
|
||||
`ResourcelessTransactionManager` if it is not.
|
||||
|===
|
||||
|
||||
You can customize any of the components described in the preceding table by creating a
|
||||
custom implementation of the `TaskConfigurer` interface. Typically, extending the
|
||||
`DefaultTaskConfigurer` (which is provided if a `TaskConfigurer` is not found) and
|
||||
overriding the required getter is sufficient. However, implementing your own from scratch
|
||||
may be required.
|
||||
|
||||
NOTE: Users should not directly use getter methods from a `TaskConfigurer` directly
|
||||
unless they are using it to supply implementations to be exposed as Spring Beans.
|
||||
|
||||
[[features-task-execution-listener]]
|
||||
=== Task Execution Listener
|
||||
|
||||
`TaskExecutionListener` lets you register listeners for specific events that occur during
|
||||
the task lifecycle. To do so, create a class that implements the
|
||||
`TaskExecutionListener` interface. The class that implements the `TaskExecutionListener`
|
||||
interface is notified of the following events:
|
||||
|
||||
* `onTaskStartup`: Prior to storing the `TaskExecution` into the `TaskRepository`.
|
||||
* `onTaskEnd`: Prior to updating the `TaskExecution` entry in the `TaskRepository` and
|
||||
marking the final state of the task.
|
||||
* `onTaskFailed`: Prior to the `onTaskEnd` method being invoked when an unhandled
|
||||
exception is thrown by the task.
|
||||
|
||||
Spring Cloud Task also lets you add `TaskExecution` Listeners to methods within a bean
|
||||
by using the following method annotations:
|
||||
|
||||
* `@BeforeTask`: Prior to the storing the `TaskExecution` into the `TaskRepository`
|
||||
* `@AfterTask`: Prior to the updating of the `TaskExecution` entry in the `TaskRepository`
|
||||
marking the final state of the task.
|
||||
* `@FailedTask`: Prior to the `@AfterTask` method being invoked when an unhandled
|
||||
exception is thrown by the task.
|
||||
|
||||
The following example shows the three annotations in use:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class MyBean {
|
||||
|
||||
@BeforeTask
|
||||
public void methodA(TaskExecution taskExecution) {
|
||||
}
|
||||
|
||||
@AfterTask
|
||||
public void methodB(TaskExecution taskExecution) {
|
||||
}
|
||||
|
||||
@FailedTask
|
||||
public void methodC(TaskExecution taskExecution, Throwable throwable) {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Inserting an `ApplicationListener` earlier in the chain than `TaskLifecycleListener` exists may cause unexpected effects.
|
||||
|
||||
[[features-task-execution-listener-Exceptions]]
|
||||
==== Exceptions Thrown by Task Execution Listener
|
||||
If an exception is thrown by a `TaskExecutionListener` event handler, all listener
|
||||
processing for that event handler stops. For example, if three `onTaskStartup` listeners
|
||||
have started and the first `onTaskStartup` event handler throws an exception, the other
|
||||
two `onTaskStartup` methods are not called. However, the other event handlers (`onTaskEnd`
|
||||
and `onTaskFailed`) for the `TaskExecutionListeners` are called.
|
||||
|
||||
The exit code returned when a exception is thrown by a `TaskExecutionListener`
|
||||
event handler is the exit code that was reported by the
|
||||
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/ExitCodeEvent.html[ExitCodeEvent].
|
||||
If no `ExitCodeEvent` is emitted, the Exception thrown is evaluated to see
|
||||
if it is of type
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit[ExitCodeGenerator].
|
||||
If so, it returns the exit code from the `ExitCodeGenerator`. Otherwise, `1`
|
||||
is returned.
|
||||
|
||||
In the case that an exception is thrown in an `onTaskStartup` method, the exit code for the application will be `1`.
|
||||
If an exception is thrown in either a `onTaskEnd` or `onTaskFailed`
|
||||
method, the exit code for the application will be the one established using the rules enumerated above.
|
||||
|
||||
NOTE: In the case of an exception being thrown in a `onTaskStartup`, `onTaskEnd`, or `onTaskFailed`
|
||||
you can not override the exit code for the application using `ExitCodeExceptionMapper`.
|
||||
|
||||
|
||||
|
||||
[[features-task-execution-listener-exit-messages]]
|
||||
==== Exit Messages
|
||||
|
||||
You can set the exit message for a task programmatically by using a
|
||||
`TaskExecutionListener`. This is done by setting the `TaskExecution's` `exitMessage`,
|
||||
which then gets passed into the `TaskExecutionListener`. The following example shows
|
||||
a method that is annotated with the `@AfterTask` `ExecutionListener` :
|
||||
|
||||
[source,java]
|
||||
@AfterTask
|
||||
public void afterMe(TaskExecution taskExecution) {
|
||||
taskExecution.setExitMessage("AFTER EXIT MESSAGE");
|
||||
}
|
||||
|
||||
An `ExitMessage` can be set at any of the listener events (`onTaskStartup`,
|
||||
`onTaskFailed`, and `onTaskEnd`). The order of precedence for the three listeners follows:
|
||||
|
||||
. `onTaskEnd`
|
||||
. `onTaskFailed`
|
||||
. `onTaskStartup`
|
||||
|
||||
For example, if you set an `exitMessage` for the `onTaskStartup` and `onTaskFailed`
|
||||
listeners and the task ends without failing, the `exitMessage` from the `onTaskStartup`
|
||||
is stored in the repository. Otherwise, if a failure occurs, the `exitMessage` from
|
||||
the `onTaskFailed` is stored. Also if you set the `exitMessage` with an
|
||||
`onTaskEnd` listener, the `exitMessage` from the `onTaskEnd` supersedes
|
||||
the exit messages from both the `onTaskStartup` and `onTaskFailed`.
|
||||
|
||||
[[features-single-instance-enabled]]
|
||||
=== Restricting Spring Cloud Task Instances
|
||||
|
||||
Spring Cloud Task lets you establish that only one task with a given task name can be run
|
||||
at a time. To do so, you need to establish the <<features-task-name, task name>> and set
|
||||
`spring.cloud.task.single-instance-enabled=true` for each task execution. While the first
|
||||
task execution is running, any other time you try to run a task with the same
|
||||
<<features-task-name, task name>> and`spring.cloud.task.single-instance-enabled=true`, the
|
||||
task fails with the following error message: `Task with name "application" is already
|
||||
running.` The default value for `spring.cloud.task.single-instance-enabled` is `false`. The
|
||||
following example shows how to set `spring.cloud.task.single-instance-enabled` to `true`:
|
||||
|
||||
`spring.cloud.task.single-instance-enabled=true or false`
|
||||
|
||||
To use this feature, you must add the following Spring Integration dependencies to your
|
||||
application:
|
||||
|
||||
[source,xml]
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
NOTE: The exit code for the application will be 1 if the task fails because this feature
|
||||
is enabled and another task is running with the same task name.
|
||||
|
||||
[[single-instance-usage-for-spring-aot-and-native-compilation]]
|
||||
==== Single Instance Usage for Spring AOT And Native Compilation
|
||||
To use Spring Cloud Task's single-instance feature when creating a natively compiled app, you need to enable the feature at build time.
|
||||
To do so, add the process-aot execution and set `spring.cloud.task.single-step-instance-enabled=true` as a JVM argument, as follows:
|
||||
[source,xml]
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>process-aot</id>
|
||||
<goals>
|
||||
<goal>process-aot</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<jvmArguments>
|
||||
-Dspring.cloud.task.single-instance-enabled=true
|
||||
</jvmArguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
[[enabling-observations-for-applicationrunner-and-commandlinerunner]]
|
||||
=== Enabling Observations for ApplicationRunner and CommandLineRunner
|
||||
|
||||
|
||||
To Enable Task Observations for `ApplicationRunner` or `CommandLineRunner` set `spring.cloud.task.observation.enabled` to true.
|
||||
|
||||
An example task application with observations enables using the `SimpleMeterRegistry` can be found https://github.com/spring-cloud/spring-cloud-task/tree/main/spring-cloud-task-samples/task-observations[here].
|
||||
|
||||
[[disabling-spring-cloud-task-auto-configuration]]
|
||||
=== Disabling Spring Cloud Task Auto Configuration
|
||||
|
||||
In cases where Spring Cloud Task should not be autoconfigured for an implementation, you can disable Task's auto configuration.
|
||||
This can be done either by adding the following annotation to your Task application:
|
||||
```
|
||||
@EnableAutoConfiguration(exclude={SimpleTaskAutoConfiguration.class})
|
||||
```
|
||||
You may also disable Task auto configuration by setting the `spring.cloud.task.autoconfiguration.enabled` property to `false`.
|
||||
|
||||
[[closing-the-context]]
|
||||
=== Closing the Context
|
||||
If the application requires the `ApplicationContext` to be closed at the
|
||||
completion of a task (all `*Runner#run` methods have been called and the task
|
||||
repository has been updated), set the property `spring.cloud.task.closecontextEnabled`
|
||||
to `true`.
|
||||
|
||||
Another case to close the context is when the Task Execution completes however the application does not terminate.
|
||||
In these cases the context is held open because a thread has been allocated
|
||||
(for example: if you are using a TaskExecutor). In these cases
|
||||
set the `spring.cloud.task.closecontextEnabled` property to `true` when launching your task.
|
||||
This will close the application's context once the task is complete.
|
||||
Thus allowing the application to terminate.
|
||||
|
||||
[[enable-task-metrics]]
|
||||
=== Enable Task Metrics
|
||||
Spring Cloud Task integrates with Micrometer and creates observations for the Tasks it executes.
|
||||
To enable Task Observability integration, you must add `spring-boot-starter-actuator`, your preferred registry implementation (if you want to publish metrics), and micrometer-tracing (if you want to publish tracing data) to your task application.
|
||||
An example maven set of dependencies to enable task observability and metrics using Influx would be:
|
||||
|
||||
[source,xml]
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-influx</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
[[spring-task-and-spring-cloud-task]]
|
||||
=== Spring Task and Spring Cloud Task Properties
|
||||
|
||||
The term `task` is frequently used word in the industry. In one such example Spring Boot offers the `spring.task` while Spring Cloud Task offers the `spring.cloud.task` properties.
|
||||
This has caused some confusion in the past that these two groups of properties are directly related. However, they represent 2 different set of features offered in the Spring ecosystem.
|
||||
|
||||
* `spring.task` refers to the properties that configure the `ThreadPoolTaskScheduler`.
|
||||
* `spring.cloud.task` refers to the properties that configure features of Spring Cloud Task.
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
[[getting-started]]
|
||||
= Getting started
|
||||
|
||||
[[partintro]]
|
||||
--
|
||||
If you are just getting started with Spring Cloud Task, you should read this section.
|
||||
Here, we answer the basic "`what?`", "`how?`", and "`why?`" questions. We start with a
|
||||
gentle introduction to Spring Cloud Task. We then build a Spring Cloud Task application,
|
||||
discussing some core principles as we go.
|
||||
--
|
||||
|
||||
[[getting-started-introducing-spring-cloud-task]]
|
||||
== Introducing Spring Cloud Task
|
||||
|
||||
Spring Cloud Task makes it easy to create short-lived microservices. It provides
|
||||
capabilities that let short-lived JVM processes be executed on demand in a production
|
||||
environment.
|
||||
|
||||
[[getting-started-system-requirements]]
|
||||
== System Requirements
|
||||
|
||||
You need to have Java installed (Java 17 or better). To build, you need to have Maven
|
||||
installed as well.
|
||||
|
||||
[[database-requirements]]
|
||||
=== Database Requirements
|
||||
|
||||
Spring Cloud Task uses a relational database to store the results of an executed task.
|
||||
While you can begin developing a task without a database (the status of the task is logged
|
||||
as part of the task repository's updates), for production environments, you want to
|
||||
use a supported database. Spring Cloud Task currently supports the following databases:
|
||||
|
||||
* DB2
|
||||
* H2
|
||||
* HSQLDB
|
||||
* MySql
|
||||
* Oracle
|
||||
* Postgres
|
||||
* SqlServer
|
||||
|
||||
[[getting-started-developing-first-task]]
|
||||
== Developing Your First Spring Cloud Task Application
|
||||
|
||||
A good place to start is with a simple "`Hello, World!`" application, so we create the
|
||||
Spring Cloud Task equivalent to highlight the features of the framework. Most IDEs have
|
||||
good support for Apache Maven, so we use it as the build tool for this project.
|
||||
|
||||
NOTE: The spring.io web site contains many https://spring.io/guides[“`Getting Started`”
|
||||
guides] that use Spring Boot. If you need to solve a specific problem, check there first.
|
||||
You can shortcut the following steps by going to the
|
||||
https://start.spring.io/[Spring Initializr] and creating a new project. Doing so
|
||||
automatically generates a new project structure so that you can start coding right away.
|
||||
We recommend experimenting with the Spring Initializr to become familiar with it.
|
||||
|
||||
[[getting-started-creating-project]]
|
||||
=== Creating the Spring Task Project using Spring Initializr
|
||||
Now we can create and test an application that prints `Hello, World!` to the console.
|
||||
|
||||
To do so:
|
||||
|
||||
. Visit the link:https://start.spring.io/[Spring Initialzr] site.
|
||||
.. Create a new Maven project with a *Group* name of `io.spring.demo` and an *Artifact* name of `helloworld`.
|
||||
.. In the Dependencies text box, type `task` and then select the `Cloud Task` dependency.
|
||||
.. In the Dependencies text box, type `jdbc` and then select the `JDBC` dependency.
|
||||
.. In the Dependencies text box, type `h2` and then select the `H2`. (or your favorite database)
|
||||
.. Click the *Generate Project* button
|
||||
. Unzip the helloworld.zip file and import the project into your favorite IDE.
|
||||
|
||||
[[getting-started-writing-the-code]]
|
||||
=== Writing the Code
|
||||
|
||||
To finish our application, we need to update the generated `HelloworldApplication` with the following contents so that it launches a Task.
|
||||
[source,java]
|
||||
----
|
||||
package io.spring.Helloworld;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.task.configuration.EnableTask;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableTask
|
||||
public class HelloworldApplication {
|
||||
|
||||
@Bean
|
||||
public ApplicationRunner applicationRunner() {
|
||||
return new HelloWorldApplicationRunner();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HelloworldApplication.class, args);
|
||||
}
|
||||
|
||||
public static class HelloWorldApplicationRunner implements ApplicationRunner {
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
System.out.println("Hello, World!");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
While it may seem small, quite a bit is going on. For more about Spring
|
||||
Boot specifics, see the
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/html/[Spring Boot reference documentation].
|
||||
|
||||
Now we can open the `application.properties` file in `src/main/resources`.
|
||||
We need to configure two properties in `application.properties`:
|
||||
|
||||
* `application.name`: To set the application name (which is translated to the task name)
|
||||
* `logging.level`: To set the logging for Spring Cloud Task to `DEBUG` in order to
|
||||
get a view of what is going on.
|
||||
|
||||
The following example shows how to do both:
|
||||
|
||||
|
||||
[source]
|
||||
----
|
||||
logging.level.org.springframework.cloud.task=DEBUG
|
||||
spring.application.name=helloWorld
|
||||
----
|
||||
|
||||
[[getting-started-at-task]]
|
||||
==== Task Auto Configuration
|
||||
|
||||
When including Spring Cloud Task Starter dependency, Task auto configures all beans to bootstrap it's functionality.
|
||||
Part of this configuration registers the `TaskRepository` and the infrastructure for its use.
|
||||
|
||||
In our demo, the `TaskRepository` uses an embedded H2 database to record the results
|
||||
of a task. This H2 embedded database is not a practical solution for a production environment, since
|
||||
the H2 DB goes away once the task ends. However, for a quick getting-started
|
||||
experience, we can use this in our example as well as echoing to the logs what is being updated
|
||||
in that repository. In the xref:features.adoc#features-configuration[Configuration] section (later in this
|
||||
documentation), we cover how to customize the configuration of the pieces provided by
|
||||
Spring Cloud Task.
|
||||
|
||||
When our sample application runs, Spring Boot launches our `HelloWorldCommandLineRunner`
|
||||
and outputs our "`Hello, World!`" message to standard out. The `TaskLifecycleListener`
|
||||
records the start of the task and the end of the task in the repository.
|
||||
|
||||
[[getting-started-main-method]]
|
||||
==== The main method
|
||||
|
||||
The main method serves as the entry point to any java application. Our main method
|
||||
delegates to Spring Boot's https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html[SpringApplication] class.
|
||||
|
||||
[[getting-started-clr]]
|
||||
==== The ApplicationRunner
|
||||
|
||||
Spring includes many ways to bootstrap an application's logic. Spring Boot provides
|
||||
a convenient method of doing so in an organized manner through its `*Runner` interfaces
|
||||
(`CommandLineRunner` or `ApplicationRunner`). A well behaved task can bootstrap any
|
||||
logic by using one of these two runners.
|
||||
|
||||
The lifecycle of a task is considered from before the `*Runner#run` methods are executed
|
||||
to once they are all complete. Spring Boot lets an application use multiple
|
||||
`*Runner` implementations, as does Spring Cloud Task.
|
||||
|
||||
NOTE: Any processing bootstrapped from mechanisms other than a `CommandLineRunner` or
|
||||
`ApplicationRunner` (by using `InitializingBean#afterPropertiesSet` for example) is not
|
||||
recorded by Spring Cloud Task.
|
||||
|
||||
[[getting-started-running-the-example]]
|
||||
=== Running the Example
|
||||
|
||||
At this point, our application should work. Since this application is Spring Boot-based,
|
||||
we can run it from the command line by using `$ mvn spring-boot:run` from the root
|
||||
of our application, as shown (with its output) in the following example:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ mvn clean spring-boot:run
|
||||
....... . . .
|
||||
....... . . . (Maven log output here)
|
||||
....... . . .
|
||||
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v2.0.3.RELEASE)
|
||||
|
||||
2018-07-23 17:44:34.426 INFO 1978 --- [ main] i.s.d.helloworld.HelloworldApplication : Starting HelloworldApplication on Glenns-MBP-2.attlocal.net with PID 1978 (/Users/glennrenfro/project/helloworld/target/classes started by glennrenfro in /Users/glennrenfro/project/helloworld)
|
||||
2018-07-23 17:44:34.430 INFO 1978 --- [ main] i.s.d.helloworld.HelloworldApplication : No active profile set, falling back to default profiles: default
|
||||
2018-07-23 17:44:34.472 INFO 1978 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1d24f32d: startup date [Mon Jul 23 17:44:34 EDT 2018]; root of context hierarchy
|
||||
2018-07-23 17:44:35.280 INFO 1978 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
|
||||
2018-07-23 17:44:35.410 INFO 1978 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
|
||||
2018-07-23 17:44:35.419 DEBUG 1978 --- [ main] o.s.c.t.c.SimpleTaskConfiguration : Using org.springframework.cloud.task.configuration.DefaultTaskConfigurer TaskConfigurer
|
||||
2018-07-23 17:44:35.420 DEBUG 1978 --- [ main] o.s.c.t.c.DefaultTaskConfigurer : No EntityManager was found, using DataSourceTransactionManager
|
||||
2018-07-23 17:44:35.522 DEBUG 1978 --- [ main] o.s.c.t.r.s.TaskRepositoryInitializer : Initializing task schema for h2 database
|
||||
2018-07-23 17:44:35.525 INFO 1978 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [org/springframework/cloud/task/schema-h2.sql]
|
||||
2018-07-23 17:44:35.558 INFO 1978 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [org/springframework/cloud/task/schema-h2.sql] in 33 ms.
|
||||
2018-07-23 17:44:35.728 INFO 1978 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
|
||||
2018-07-23 17:44:35.730 INFO 1978 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Bean with name 'dataSource' has been autodetected for JMX exposure
|
||||
2018-07-23 17:44:35.733 INFO 1978 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
|
||||
2018-07-23 17:44:35.738 INFO 1978 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0
|
||||
2018-07-23 17:44:35.762 DEBUG 1978 --- [ main] o.s.c.t.r.support.SimpleTaskRepository : Creating: TaskExecution{executionId=0, parentExecutionId=null, exitCode=null, taskName='application', startTime=Mon Jul 23 17:44:35 EDT 2018, endTime=null, exitMessage='null', externalExecutionId='null', errorMessage='null', arguments=[]}
|
||||
2018-07-23 17:44:35.772 INFO 1978 --- [ main] i.s.d.helloworld.HelloworldApplication : Started HelloworldApplication in 1.625 seconds (JVM running for 4.764)
|
||||
Hello, World!
|
||||
2018-07-23 17:44:35.782 DEBUG 1978 --- [ main] o.s.c.t.r.support.SimpleTaskRepository : Updating: TaskExecution with executionId=1 with the following {exitCode=0, endTime=Mon Jul 23 17:44:35 EDT 2018, exitMessage='null', errorMessage='null'}
|
||||
----
|
||||
|
||||
The preceding output has three lines that of interest to us here:
|
||||
|
||||
* `SimpleTaskRepository` logged the creation of the entry in the `TaskRepository`.
|
||||
* The execution of our `CommandLineRunner`, demonstrated by the "`Hello, World!`" output.
|
||||
* `SimpleTaskRepository` logs the completion of the task in the `TaskRepository`.
|
||||
|
||||
NOTE: A simple task application can be found in the samples module of the Spring Cloud
|
||||
Task Project
|
||||
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-samples/timestamp[here].
|
||||
@@ -1,46 +0,0 @@
|
||||
[[preface]]
|
||||
= Preface
|
||||
|
||||
[[task-documentation-about]]
|
||||
|
||||
This section provides a brief overview of the Spring Cloud Task reference documentation.
|
||||
Think of it as a map for the rest of the document. You can read this reference guide in a
|
||||
linear fashion or you can skip sections if something does not interest you.
|
||||
|
||||
[[about-the-documentation]]
|
||||
== About the documentation
|
||||
The Spring Cloud Task reference guide is available in {spring-cloud-task-docs}[html]
|
||||
and {spring-cloud-task-docs}/index.pdf[pdf]. The latest copy is available at
|
||||
{spring-cloud-task-docs-current}.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others,
|
||||
provided that you do not charge any fee for such copies and further provided that each
|
||||
copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
[[task-documentation-getting-help]]
|
||||
== Getting help
|
||||
Having trouble with Spring Cloud Task? We would like to help!
|
||||
|
||||
* Ask a question. We monitor https://stackoverflow.com[stackoverflow.com] for questions
|
||||
tagged with https://stackoverflow.com/tags/spring-cloud-task[`spring-cloud-task`].
|
||||
* Report bugs with Spring Cloud Task at
|
||||
https://github.com/spring-cloud/spring-cloud-task/issues.
|
||||
|
||||
NOTE: All of Spring Cloud Task is open source, including the documentation. If you find
|
||||
a problem with the docs or if you just want to improve them, please {github-code}[get
|
||||
involved].
|
||||
|
||||
[[task-documentation-first-steps]]
|
||||
== First Steps
|
||||
If you are just getting started with Spring Cloud Task or with 'Spring' in general, we
|
||||
suggesting reading the xref:getting-started.adoc[Getting started] chapter.
|
||||
|
||||
To get started from scratch, read the following sections:
|
||||
|
||||
* xref:getting-started.adoc#getting-started-introducing-spring-cloud-task[Introducing Spring Cloud Task]
|
||||
* xref:getting-started.adoc#getting-started-system-requirements[System Requirements] +
|
||||
|
||||
To follow the tutorial, read
|
||||
xref:getting-started.adoc#getting-started-developing-first-task[Developing Your First Spring Cloud Task Application] +
|
||||
To run your example, read
|
||||
xref:getting-started.adoc#getting-started-running-the-example[Running the Example]
|
||||
@@ -1,2 +0,0 @@
|
||||
Spring Cloud Task allows a user to develop and run short lived microservices using Spring Cloud and run them locally, in the cloud, even on Spring Cloud Data Flow. Just add `@EnableTask` and run your app as a Spring Boot app (single application context).
|
||||
If you are new to Spring Cloud Task, take a look at our https://docs.spring.io/spring-cloud-task/docs/2.0.0.RELEASE/reference/htmlsingle/#getting-started[Getting Started] docs.
|
||||
@@ -1,25 +0,0 @@
|
||||
[[spring-cloud-task-reference-guide]]
|
||||
= Spring Cloud Task Reference Guide
|
||||
:page-section-summary-toc: 1
|
||||
Michael Minella, Glenn Renfro, Jay Bryant
|
||||
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
Version {project-version}
|
||||
|
||||
(C) 2009-2022 VMware, Inc. All rights reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further
|
||||
provided that each copy contains this Copyright Notice, whether distributed in
|
||||
print or electronically.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ======================================================================================
|
||||
@@ -1,216 +0,0 @@
|
||||
[[stream-integration]]
|
||||
= Spring Cloud Stream Integration
|
||||
|
||||
[[partintro]]
|
||||
--
|
||||
A task by itself can be useful, but integration of a task into a larger ecosystem lets it
|
||||
be useful for more complex processing and orchestration. This section
|
||||
covers the integration options for Spring Cloud Task with Spring Cloud Stream.
|
||||
--
|
||||
|
||||
[[stream-integration-launching-sink]]
|
||||
== Launching a Task from a Spring Cloud Stream
|
||||
|
||||
You can launch tasks from a stream. To do so, create a sink that listens for a message
|
||||
that contains a `TaskLaunchRequest` as its payload. The `TaskLaunchRequest` contains:
|
||||
|
||||
* `uri`: To the task artifact that is to be executed.
|
||||
* `applicationName`: The name that is associated with the task. If no
|
||||
applicationName is set, the `TaskLaunchRequest` generates a task name
|
||||
comprised of the following: `Task-<UUID>`.
|
||||
* `commandLineArguments`: A list containing the command line arguments for the task.
|
||||
* `environmentProperties`: A map containing the environment variables to be used by the
|
||||
task.
|
||||
* `deploymentProperties`: A map containing the properties that are used by the deployer to
|
||||
deploy the task.
|
||||
|
||||
NOTE: If the payload is of a different type, the sink throws an exception.
|
||||
|
||||
For example, a stream can be created that has a processor that takes in data from an
|
||||
HTTP source and creates a `GenericMessage` that contains the `TaskLaunchRequest` and sends
|
||||
the message to its output channel. The task sink would then receive the message from its
|
||||
input channel and then launch the task.
|
||||
|
||||
To create a taskSink, you need only create a Spring Boot application that includes the
|
||||
`EnableTaskLauncher` annotation, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@EnableTaskLauncher
|
||||
public class TaskSinkApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TaskSinkApplication.class, args);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-samples[samples
|
||||
module] of the Spring Cloud Task project contains a sample Sink and Processor. To install
|
||||
these samples into your local maven repository, run a maven build from the
|
||||
`spring-cloud-task-samples` directory with the `skipInstall` property set to `false`, as
|
||||
shown in the following example:
|
||||
|
||||
`mvn clean install`
|
||||
|
||||
NOTE: The `maven.remoteRepositories.springRepo.url` property must be set to the location
|
||||
of the remote repository in which the über-jar is located. If not set, there is no remote
|
||||
repository, so it relies upon the local repository only.
|
||||
|
||||
[[stream-integration-launching-sink-dataflow]]
|
||||
=== Spring Cloud Data Flow
|
||||
|
||||
To create a stream in Spring Cloud Data Flow, you must first register the Task Sink
|
||||
Application we created. In the following example, we are registering the Processor and
|
||||
Sink sample applications by using the Spring Cloud Data Flow shell:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
app register --name taskSink --type sink --uri maven://io.spring.cloud:tasksink:<version>
|
||||
app register --name taskProcessor --type processor --uri maven:io.spring.cloud:taskprocessor:<version>
|
||||
----
|
||||
|
||||
The following example shows how to create a stream from the Spring Cloud Data Flow shell:
|
||||
|
||||
[source,bash]
|
||||
stream create foo --definition "http --server.port=9000|taskProcessor|taskSink" --deploy
|
||||
|
||||
[[stream-integration-events]]
|
||||
== Spring Cloud Task Events
|
||||
|
||||
Spring Cloud Task provides the ability to emit events through a Spring Cloud Stream
|
||||
channel when the task is run through a Spring Cloud Stream channel. A task listener is
|
||||
used to publish the `TaskExecution` on a message channel named `task-events`. This feature
|
||||
is autowired into any task that has `spring-cloud-stream`, `spring-cloud-stream-<binder>`,
|
||||
and a defined task on its classpath.
|
||||
|
||||
NOTE: To disable the event emitting listener, set the `spring.cloud.task.events.enabled`
|
||||
property to `false`.
|
||||
|
||||
With the appropriate classpath defined, the following task emits the `TaskExecution` as an
|
||||
event on the `task-events` channel (at both the start and the end of the task):
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class TaskEventsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TaskEventsApplication.class, args);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class TaskConfiguration {
|
||||
|
||||
@Bean
|
||||
public ApplicationRunner applicationRunner() {
|
||||
return new ApplicationRunner() {
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
System.out.println("The ApplicationRunner was executed");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: A binder implementation is also required to be on the classpath.
|
||||
|
||||
NOTE: A sample task event application can be found in the samples module
|
||||
of the Spring Cloud Task Project,
|
||||
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-samples/task-events[here].
|
||||
|
||||
[[stream-integration-disable-task-events]]
|
||||
=== Disabling Specific Task Events
|
||||
|
||||
To disable task events, you can set the `spring.cloud.task.events.enabled` property to
|
||||
`false`.
|
||||
|
||||
[[stream-integration-batch-events]]
|
||||
== Spring Batch Events
|
||||
|
||||
When executing a Spring Batch job through a task, Spring Cloud Task can be configured to
|
||||
emit informational messages based on the Spring Batch listeners available in Spring Batch.
|
||||
Specifically, the following Spring Batch listeners are autoconfigured into each batch job
|
||||
and emit messages on the associated Spring Cloud Stream channels when run through Spring
|
||||
Cloud Task:
|
||||
|
||||
* `JobExecutionListener` listens for `job-execution-events`
|
||||
* `StepExecutionListener` listens for `step-execution-events`
|
||||
* `ChunkListener` listens for `chunk-events`
|
||||
* `ItemReadListener` listens for `item-read-events`
|
||||
* `ItemProcessListener` listens for `item-process-events`
|
||||
* `ItemWriteListener` listens for `item-write-events`
|
||||
* `SkipListener` listens for `skip-events`
|
||||
|
||||
These listeners are autoconfigured into any `AbstractJob` when the appropriate
|
||||
beans (a `Job` and a `TaskLifecycleListener`) exist in the context. Configuration to
|
||||
listen to these events is handled the same way binding to any other Spring
|
||||
Cloud Stream channel is done. Our task (the one running the batch job) serves as a
|
||||
`Source`, with the listening applications serving as either a `Processor` or a `Sink`.
|
||||
|
||||
An example could be to have an application listening to the `job-execution-events` channel
|
||||
for the start and stop of a job. To configure the listening application, you would
|
||||
configure the input to be `job-execution-events` as follows:
|
||||
|
||||
`spring.cloud.stream.bindings.input.destination=job-execution-events`
|
||||
|
||||
NOTE: A binder implementation is also required to be on the classpath.
|
||||
|
||||
NOTE: A sample batch event application can be found in the samples module
|
||||
of the Spring Cloud Task Project,
|
||||
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-samples/batch-events[here].
|
||||
|
||||
[[sending-batch-events-to-different-channels]]
|
||||
=== Sending Batch Events to Different Channels
|
||||
|
||||
One of the options that Spring Cloud Task offers for batch events is the ability to alter
|
||||
the channel to which a specific listener can emit its messages. To do so, use the
|
||||
following configuration:
|
||||
`spring.cloud.stream.bindings.<the channel>.destination=<new destination>`. For example,
|
||||
if `StepExecutionListener` needs to emit its messages to another channel called
|
||||
`my-step-execution-events` instead of the default `step-execution-events`, you can add the
|
||||
following configuration:
|
||||
|
||||
`spring.cloud.task.batch.events.step-execution-events-binding-name=my-step-execution-events`
|
||||
|
||||
[[disabling-batch-events]]
|
||||
=== Disabling Batch Events
|
||||
To disable the listener functionality for all batch events, use the following
|
||||
configuration:
|
||||
|
||||
`spring.cloud.task.batch.events.enabled=false`
|
||||
|
||||
To disable a specific batch event, use the following configuration:
|
||||
|
||||
`spring.cloud.task.batch.events.<batch event listener>.enabled=false`:
|
||||
|
||||
The following listing shows individual listeners that you can disable:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
spring.cloud.task.batch.events.job-execution.enabled=false
|
||||
spring.cloud.task.batch.events.step-execution.enabled=false
|
||||
spring.cloud.task.batch.events.chunk.enabled=false
|
||||
spring.cloud.task.batch.events.item-read.enabled=false
|
||||
spring.cloud.task.batch.events.item-process.enabled=false
|
||||
spring.cloud.task.batch.events.item-write.enabled=false
|
||||
spring.cloud.task.batch.events.skip.enabled=false
|
||||
----
|
||||
|
||||
[[emit-order-for-batch-events]]
|
||||
=== Emit Order for Batch Events
|
||||
By default, batch events have `Ordered.LOWEST_PRECEDENCE`. To change this value (for
|
||||
example, to 5 ), use the following configuration:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
spring.cloud.task.batch.events.job-execution-order=5
|
||||
spring.cloud.task.batch.events.step-execution-order=5
|
||||
spring.cloud.task.batch.events.chunk-order=5
|
||||
spring.cloud.task.batch.events.item-read-order=5
|
||||
spring.cloud.task.batch.events.item-process-order=5
|
||||
spring.cloud.task.batch.events.item-write-order=5
|
||||
spring.cloud.task.batch.events.skip-order=5
|
||||
----
|
||||
120
docs/pom.xml
120
docs/pom.xml
@@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-parent</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-task-docs</artifactId>
|
||||
<name>Spring Cloud Task Docs</name>
|
||||
<description>Spring Cloud Task Docs</description>
|
||||
<properties>
|
||||
<docs.main>spring-cloud-task</docs.main>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<configprops.inclusionPattern>spring.cloud.task.*</configprops.inclusionPattern>
|
||||
<upload-docs-zip.phase>deploy</upload-docs-zip.phase>
|
||||
<asciidoctorj.pdf.version>1.5.0-alpha.16</asciidoctorj.pdf.version>
|
||||
|
||||
<!-- Observability -->
|
||||
<micrometer-docs-generator.version>1.0.0</micrometer-docs-generator.version>
|
||||
<micrometer-docs-generator.inputPath>${maven.multiModuleProjectDirectory}/spring-cloud-task-core</micrometer-docs-generator.inputPath>
|
||||
<micrometer-docs-generator.inclusionPattern>.*</micrometer-docs-generator.inclusionPattern>
|
||||
<micrometer-docs-generator.outputPath>${maven.multiModuleProjectDirectory}/target/</micrometer-docs-generator.outputPath>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-cloud-starter-single-step-batch-job</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-cloud-starter-task</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<sourceDirectory>src/main/asciidoc</sourceDirectory>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>docs</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-docs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<mainClass>
|
||||
io.micrometer.docs.DocsGeneratorCommand
|
||||
</mainClass>
|
||||
<includePluginDependencies>true</includePluginDependencies>
|
||||
<arguments>
|
||||
<argument>${micrometer-docs-generator.inputPath}</argument>
|
||||
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
|
||||
<argument>${micrometer-docs-generator.outputPath}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-docs-generator</artifactId>
|
||||
<version>${micrometer-docs-generator.version}</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copying-javadocs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<target>
|
||||
<mkdir dir="${project.basedir}/target/generated-docs/apidocs"/>
|
||||
</target>
|
||||
<target>
|
||||
<copy todir="${project.basedir}/target/generated-docs/apidocs">
|
||||
<fileset dir="${maven.multiModuleProjectDirectory}/target/site/apidocs"/>
|
||||
</copy>
|
||||
</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
2
docs/src/main/asciidoc/.gitignore
vendored
2
docs/src/main/asciidoc/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*.html
|
||||
*.css
|
||||
@@ -1,20 +0,0 @@
|
||||
require 'asciidoctor'
|
||||
require 'erb'
|
||||
|
||||
guard 'shell' do
|
||||
watch(/.*\.adoc$/) {|m|
|
||||
Asciidoctor.render_file('index.adoc', \
|
||||
:in_place => true, \
|
||||
:safe => Asciidoctor::SafeMode::UNSAFE, \
|
||||
:attributes => {\
|
||||
'source-highlighter' => 'prettify', \
|
||||
'icons' => 'font', \
|
||||
'linkcss' => 'true', \
|
||||
'copycss' => 'true', \
|
||||
'doctype' => 'book'})
|
||||
}
|
||||
end
|
||||
|
||||
guard 'livereload' do
|
||||
watch(%r{^.+\.(css|js|html)$})
|
||||
end
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@@ -1 +0,0 @@
|
||||
include::spring-cloud-task.adoc[]
|
||||
@@ -1,29 +0,0 @@
|
||||
= Spring Cloud Task Reference Guide
|
||||
Michael Minella, Glenn Renfro, Jay Bryant
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
(C) 2009-2020 VMware, Inc. All rights reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further
|
||||
provided that each copy contains this Copyright Notice, whether distributed in
|
||||
print or electronically.
|
||||
|
||||
include::preface.adoc[leveloffset=+1]
|
||||
|
||||
include::getting-started.adoc[leveloffset=+1]
|
||||
|
||||
include::features.adoc[leveloffset=+1]
|
||||
|
||||
include::batch.adoc[leveloffset=+1]
|
||||
|
||||
include::batch-starter.adoc[leveloffset=+1]
|
||||
|
||||
include::stream.adoc[leveloffset=+1]
|
||||
|
||||
include::appendix.adoc[leveloffset=+1]
|
||||
|
||||
// ======================================================================================
|
||||
@@ -1 +0,0 @@
|
||||
include::spring-cloud-task.pdfadoc[]
|
||||
@@ -1,29 +0,0 @@
|
||||
= Spring Cloud Task Reference Guide
|
||||
Michael Minella, Glenn Renfro, Jay Bryant
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
(C) 2009-2022 VMware, Inc. All rights reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further
|
||||
provided that each copy contains this Copyright Notice, whether distributed in
|
||||
print or electronically.
|
||||
|
||||
include::preface.adoc[leveloffset=+1]
|
||||
|
||||
include::getting-started.adoc[leveloffset=+1]
|
||||
|
||||
include::features.adoc[leveloffset=+1]
|
||||
|
||||
include::batch.adoc[leveloffset=+1]
|
||||
|
||||
include::batch-starter.adoc[leveloffset=+1]
|
||||
|
||||
include::stream.adoc[leveloffset=+1]
|
||||
|
||||
include::appendix.adoc[leveloffset=+1]
|
||||
|
||||
// ======================================================================================
|
||||
@@ -1,29 +0,0 @@
|
||||
= Spring Cloud Task Reference Guide
|
||||
Michael Minella, Glenn Renfro, Jay Bryant
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
(C) 2009-2020 VMware, Inc. All rights reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further
|
||||
provided that each copy contains this Copyright Notice, whether distributed in
|
||||
print or electronically.
|
||||
|
||||
include::preface.adoc[leveloffset=+1]
|
||||
|
||||
include::getting-started.adoc[leveloffset=+1]
|
||||
|
||||
include::features.adoc[leveloffset=+1]
|
||||
|
||||
include::batch.adoc[leveloffset=+1]
|
||||
|
||||
include::batch-starter.adoc[leveloffset=+1]
|
||||
|
||||
include::stream.adoc[leveloffset=+1]
|
||||
|
||||
include::appendix.adoc[leveloffset=+1]
|
||||
|
||||
// ======================================================================================
|
||||
@@ -1,29 +0,0 @@
|
||||
= Spring Cloud Task Reference Guide
|
||||
Michael Minella, Glenn Renfro, Jay Bryant
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
(C) 2009-2022 VMware, Inc. All rights reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further
|
||||
provided that each copy contains this Copyright Notice, whether distributed in
|
||||
print or electronically.
|
||||
|
||||
include::preface.adoc[leveloffset=+1]
|
||||
|
||||
include::getting-started.adoc[leveloffset=+1]
|
||||
|
||||
include::features.adoc[leveloffset=+1]
|
||||
|
||||
include::batch.adoc[leveloffset=+1]
|
||||
|
||||
include::batch-starter.adoc[leveloffset=+1]
|
||||
|
||||
include::stream.adoc[leveloffset=+1]
|
||||
|
||||
include::appendix.adoc[leveloffset=+1]
|
||||
|
||||
// ======================================================================================
|
||||
@@ -1,695 +0,0 @@
|
||||
/* Javadoc style sheet */
|
||||
/*
|
||||
Overall document style
|
||||
*/
|
||||
@import url('resources/fonts/dejavu.css');
|
||||
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
color: #353833;
|
||||
font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
text-decoration: none;
|
||||
color: #4A6782;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
text-decoration: none;
|
||||
color: #bb7a2a;
|
||||
}
|
||||
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
color: #4A6782;
|
||||
}
|
||||
|
||||
a[name] {
|
||||
color: #353833;
|
||||
}
|
||||
|
||||
a[name]:hover {
|
||||
text-decoration: none;
|
||||
color: #353833;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'DejaVu Sans Mono', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
code, tt {
|
||||
font-family: 'DejaVu Sans Mono', monospace;
|
||||
font-size: 14px;
|
||||
padding-top: 4px;
|
||||
margin-top: 8px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
dt code {
|
||||
font-family: 'DejaVu Sans Mono', monospace;
|
||||
font-size: 14px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
table tr td dt code {
|
||||
font-family: 'DejaVu Sans Mono', monospace;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
sup {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
/*
|
||||
Document title and Copyright styles
|
||||
*/
|
||||
.clear {
|
||||
clear: both;
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aboutLanguage {
|
||||
float: right;
|
||||
padding: 0px 21px;
|
||||
font-size: 11px;
|
||||
z-index: 200;
|
||||
margin-top: -9px;
|
||||
}
|
||||
|
||||
.legalCopy {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.bar a, .bar a:link, .bar a:visited, .bar a:active {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.bar a:hover, .bar a:focus {
|
||||
color: #bb7a2a;
|
||||
}
|
||||
|
||||
.tab {
|
||||
background-color: #0066FF;
|
||||
color: #ffffff;
|
||||
padding: 8px;
|
||||
width: 5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*
|
||||
Navigation bar styles
|
||||
*/
|
||||
.bar {
|
||||
background-color: #4D7A97;
|
||||
color: #FFFFFF;
|
||||
padding: .8em .5em .4em .8em;
|
||||
height: auto; /*height:1.8em;*/
|
||||
font-size: 11px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.topNav {
|
||||
background-color: #4D7A97;
|
||||
color: #FFFFFF;
|
||||
float: left;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
clear: right;
|
||||
height: 2.8em;
|
||||
padding-top: 10px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bottomNav {
|
||||
margin-top: 10px;
|
||||
background-color: #4D7A97;
|
||||
color: #FFFFFF;
|
||||
float: left;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
clear: right;
|
||||
height: 2.8em;
|
||||
padding-top: 10px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.subNav {
|
||||
background-color: #dee3e9;
|
||||
float: left;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.subNav div {
|
||||
clear: left;
|
||||
float: left;
|
||||
padding: 0 0 5px 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
ul.navList, ul.subNavList {
|
||||
float: left;
|
||||
margin: 0 25px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.navList li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
padding: 5px 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
ul.subNavList li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.topNav a:hover, .bottomNav a:hover {
|
||||
text-decoration: none;
|
||||
color: #bb7a2a;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.navBarCell1Rev {
|
||||
background-color: #F8981D;
|
||||
color: #253441;
|
||||
margin: auto 5px;
|
||||
}
|
||||
|
||||
.skipNav {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
left: -9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*
|
||||
Page header and footer styles
|
||||
*/
|
||||
.header, .footer {
|
||||
clear: both;
|
||||
margin: 0 20px;
|
||||
padding: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.indexHeader {
|
||||
margin: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.indexHeader span {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.indexHeader h1 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #2c4557;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.header ul {
|
||||
margin: 0 0 15px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer ul {
|
||||
margin: 20px 0 5px 0;
|
||||
}
|
||||
|
||||
.header ul li, .footer ul li {
|
||||
list-style: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/*
|
||||
Heading styles
|
||||
*/
|
||||
div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 {
|
||||
background-color: #dee3e9;
|
||||
border: 1px solid #d0d9e0;
|
||||
margin: 0 0 6px -8px;
|
||||
padding: 7px 5px;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList ul.blockList li.blockList h3 {
|
||||
background-color: #dee3e9;
|
||||
border: 1px solid #d0d9e0;
|
||||
margin: 0 0 6px -8px;
|
||||
padding: 7px 5px;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList li.blockList h3 {
|
||||
padding: 0;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
ul.blockList li.blockList h2 {
|
||||
padding: 0px 0 20px 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Page layout container styles
|
||||
*/
|
||||
.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer {
|
||||
clear: both;
|
||||
padding: 10px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.indexContainer {
|
||||
margin: 10px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.indexContainer h2 {
|
||||
font-size: 13px;
|
||||
padding: 0 0 3px 0;
|
||||
}
|
||||
|
||||
.indexContainer ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.indexContainer ul li {
|
||||
list-style: none;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin: 10px 0 0 0;
|
||||
color: #4E4E4E;
|
||||
}
|
||||
|
||||
.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd {
|
||||
margin: 5px 0 10px 0px;
|
||||
font-size: 14px;
|
||||
font-family: 'DejaVu Sans Mono', monospace;
|
||||
}
|
||||
|
||||
.serializedFormContainer dl.nameValue dt {
|
||||
margin-left: 1px;
|
||||
font-size: 1.1em;
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.serializedFormContainer dl.nameValue dd {
|
||||
margin: 0 0 0 1px;
|
||||
font-size: 1.1em;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/*
|
||||
List styles
|
||||
*/
|
||||
ul.horizontal li {
|
||||
display: inline;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
ul.inheritance {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.inheritance li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul.inheritance li ul.inheritance {
|
||||
margin-left: 15px;
|
||||
padding-left: 15px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
ul.blockList, ul.blockListLast {
|
||||
margin: 10px 0 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.blockList li.blockList, ul.blockListLast li.blockList {
|
||||
list-style: none;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList {
|
||||
padding: 0px 20px 5px 10px;
|
||||
border: 1px solid #ededed;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList {
|
||||
padding: 0 0 5px 8px;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList ul.blockList ul.blockList li.blockList {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
padding-bottom: 15px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast {
|
||||
list-style: none;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
table tr td dl, table tr td dl dt, table tr td dl dd {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/*
|
||||
Table styles
|
||||
*/
|
||||
.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary {
|
||||
width: 100%;
|
||||
border-left: 1px solid #EEE;
|
||||
border-right: 1px solid #EEE;
|
||||
border-bottom: 1px solid #EEE;
|
||||
}
|
||||
|
||||
.overviewSummary, .memberSummary {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.overviewSummary caption, .memberSummary caption, .typeSummary caption,
|
||||
.useSummary caption, .constantsSummary caption, .deprecatedSummary caption {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
background-repeat: no-repeat;
|
||||
color: #253441;
|
||||
font-weight: bold;
|
||||
clear: none;
|
||||
overflow: hidden;
|
||||
padding: 0px;
|
||||
padding-top: 10px;
|
||||
padding-left: 1px;
|
||||
margin: 0px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link,
|
||||
.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link,
|
||||
.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover,
|
||||
.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover,
|
||||
.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active,
|
||||
.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active,
|
||||
.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited,
|
||||
.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span,
|
||||
.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span {
|
||||
white-space: nowrap;
|
||||
padding-top: 5px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 7px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
background-color: #F8981D;
|
||||
border: none;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.memberSummary caption span.activeTableTab span {
|
||||
white-space: nowrap;
|
||||
padding-top: 5px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
background-color: #F8981D;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.memberSummary caption span.tableTab span {
|
||||
white-space: nowrap;
|
||||
padding-top: 5px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
background-color: #4D7A97;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab {
|
||||
padding-top: 0px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
background-image: none;
|
||||
float: none;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd,
|
||||
.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd {
|
||||
display: none;
|
||||
width: 5px;
|
||||
position: relative;
|
||||
float: left;
|
||||
background-color: #F8981D;
|
||||
}
|
||||
|
||||
.memberSummary .activeTableTab .tabEnd {
|
||||
display: none;
|
||||
width: 5px;
|
||||
margin-right: 3px;
|
||||
position: relative;
|
||||
float: left;
|
||||
background-color: #F8981D;
|
||||
}
|
||||
|
||||
.memberSummary .tableTab .tabEnd {
|
||||
display: none;
|
||||
width: 5px;
|
||||
margin-right: 3px;
|
||||
position: relative;
|
||||
background-color: #4D7A97;
|
||||
float: left;
|
||||
|
||||
}
|
||||
|
||||
.overviewSummary td, .memberSummary td, .typeSummary td,
|
||||
.useSummary td, .constantsSummary td, .deprecatedSummary td {
|
||||
text-align: left;
|
||||
padding: 0px 0px 12px 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th,
|
||||
td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td {
|
||||
vertical-align: top;
|
||||
padding-right: 0px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
th.colFirst, th.colLast, th.colOne, .constantsSummary th {
|
||||
background: #dee3e9;
|
||||
text-align: left;
|
||||
padding: 8px 3px 3px 7px;
|
||||
}
|
||||
|
||||
td.colFirst, th.colFirst {
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
td.colLast, th.colLast {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
td.colOne, th.colOne {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.overviewSummary td.colFirst, .overviewSummary th.colFirst,
|
||||
.overviewSummary td.colOne, .overviewSummary th.colOne,
|
||||
.memberSummary td.colFirst, .memberSummary th.colFirst,
|
||||
.memberSummary td.colOne, .memberSummary th.colOne,
|
||||
.typeSummary td.colFirst {
|
||||
width: 25%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tableSubHeadingColor {
|
||||
background-color: #EEEEFF;
|
||||
}
|
||||
|
||||
.altColor {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.rowColor {
|
||||
background-color: #EEEEEF;
|
||||
}
|
||||
|
||||
/*
|
||||
Content styles
|
||||
*/
|
||||
.description pre {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.deprecatedContent {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.docSummary {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList ul.blockList li.blockList h3 {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.block {
|
||||
font-size: 14px;
|
||||
font-family: 'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
|
||||
}
|
||||
|
||||
td.colLast div {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
|
||||
td.colLast a {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
/*
|
||||
Formatting effect styles
|
||||
*/
|
||||
.sourceLineNo {
|
||||
color: green;
|
||||
padding: 0 30px 0 0;
|
||||
}
|
||||
|
||||
h1.hidden {
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
margin: 3px 10px 2px 0px;
|
||||
color: #474747;
|
||||
}
|
||||
|
||||
.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink,
|
||||
.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel,
|
||||
.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.deprecationComment, .emphasizedPhrase, .interfaceName {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase,
|
||||
div.block div.block span.interfaceName {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.contentContainer ul.blockList li.blockList h2 {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Spring
|
||||
*/
|
||||
|
||||
pre.code {
|
||||
background-color: #F8F8F8;
|
||||
border: 1px solid #CCCCCC;
|
||||
border-radius: 3px 3px 3px 3px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
margin: 4px 20px 2px 0px;
|
||||
}
|
||||
|
||||
pre.code code, pre.code code * {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre.code code, pre.code code * {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
191
mvnw
vendored
191
mvnw
vendored
@@ -8,7 +8,7 @@
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
@@ -19,7 +19,7 @@
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
# Maven Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
@@ -54,38 +54,16 @@ case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
#
|
||||
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
|
||||
# for the new JDKs provided by Oracle.
|
||||
#
|
||||
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
|
||||
#
|
||||
# Oracle JDKs
|
||||
#
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=`/usr/libexec/java_home`
|
||||
fi
|
||||
;;
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
@@ -130,13 +108,12 @@ if $cygwin ; then
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Migwn, ensure paths are in UNIX format before anything is touched
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
@@ -184,27 +161,28 @@ fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
local basedir=$(pwd)
|
||||
local wdir=$(pwd)
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
wdir=$(cd "$wdir/.."; pwd)
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
@@ -216,9 +194,108 @@ concat_lines() {
|
||||
fi
|
||||
}
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||
else
|
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
@@ -226,20 +303,8 @@ export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
echo "Running version check"
|
||||
VERSION=$( sed '\!<parent!,\!</parent!d' `dirname $0`/pom.xml | grep '<version' | head -1 | sed -e 's/.*<version>//' -e 's!</version>.*$!!' )
|
||||
echo "The found version is [${VERSION}]"
|
||||
|
||||
if echo $VERSION | egrep -q 'M|RC'; then
|
||||
echo Activating \"milestone\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone"
|
||||
else
|
||||
echo Deactivating \"milestone\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@"
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
|
||||
325
mvnw.cmd
vendored
325
mvnw.cmd
vendored
@@ -1,145 +1,182 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
%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%
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
|
||||
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
|
||||
368
pom.xml
Executable file → Normal file
368
pom.xml
Executable file → Normal file
@@ -1,342 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-parent</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud Task Build</name>
|
||||
<description>Spring Cloud Task Build</description>
|
||||
<url>https://cloud.spring.io/spring-cloud-task/</url>
|
||||
<organization>
|
||||
<name>VMware, Inc.</name>
|
||||
<url>https://www.spring.io</url>
|
||||
</organization>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<artifactId>spring-cloud-task-docs-build</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<name>Spring Cloud Task Docs Build</name>
|
||||
<description>Builds Spring Cloud Task Docs.</description>
|
||||
<url>https://spring.io/projects/spring-cloud-task</url>
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/spring-cloud/spring-cloud-task.git
|
||||
</connection>
|
||||
<developerConnection>
|
||||
scm:git:git@github.com:spring-cloud/spring-cloud-task.git
|
||||
</developerConnection>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-task</url>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>mminella</id>
|
||||
<name>Michael Minella</name>
|
||||
<email>mminella at vmware.com</email>
|
||||
<organization>VMware, Inc.</organization>
|
||||
<organizationUrl>https://www.spring.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Project Lead</role>
|
||||
</roles>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>cppwfs</id>
|
||||
<name>Glenn Renfro</name>
|
||||
<email>grenfro at vmware</email>
|
||||
<organization>VMware, Inc.</organization>
|
||||
<organizationUrl>https://www.spring.io</organizationUrl>
|
||||
</developer>
|
||||
</developers>
|
||||
<prerequisites>
|
||||
<maven>3.2.1</maven>
|
||||
</prerequisites>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-dependencies</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-deployer-dependencies</artifactId>
|
||||
<version>${spring-cloud-deployer.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-deployer-local</artifactId>
|
||||
<version>${spring-cloud-deployer-local.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<version>${spring-cloud-stream-binder-rabbit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-task-dependencies</module>
|
||||
<module>spring-cloud-task-core</module>
|
||||
<module>spring-cloud-task-batch</module>
|
||||
<module>spring-cloud-task-stream</module>
|
||||
<module>spring-cloud-starter-task</module>
|
||||
<module>spring-cloud-task-samples</module>
|
||||
<module>spring-cloud-task-integration-tests</module>
|
||||
<module>docs</module>
|
||||
<module>spring-cloud-starter-single-step-batch-job</module>
|
||||
</modules>
|
||||
<issueManagement>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-task/issues</url>
|
||||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
|
||||
<spring-cloud-stream.version>4.1.0-SNAPSHOT</spring-cloud-stream.version>
|
||||
<spring-cloud-deployer.version>2.8.3</spring-cloud-deployer.version>
|
||||
<spring-cloud-deployer-local.version>2.8.3
|
||||
</spring-cloud-deployer-local.version>
|
||||
<spring-cloud-stream-binder-rabbit.version>${spring-cloud-stream.version}
|
||||
</spring-cloud-stream-binder-rabbit.version>
|
||||
<jakarta-ee-api.version>10.0.0</jakarta-ee-api.version>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<sonar.jacoco.reportPath>
|
||||
${project.build.directory}/coverage-reports/jacoco-ut.exec
|
||||
</sonar.jacoco.reportPath>
|
||||
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
|
||||
<maven-checkstyle-plugin.failsOnViolation>true
|
||||
</maven-checkstyle-plugin.failsOnViolation>
|
||||
<maven-checkstyle-plugin.includeTestSourceDirectory>true
|
||||
</maven-checkstyle-plugin.includeTestSourceDirectory>
|
||||
<java.version>17</java.version>
|
||||
<spring-javaformat-maven-plugin.version>0.0.34</spring-javaformat-maven-plugin.version>
|
||||
<io.spring.maven.antora-version>0.0.3</io.spring.maven.antora-version>
|
||||
</properties>
|
||||
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<groupId>io.spring.maven.antora</groupId>
|
||||
<artifactId>antora-maven-plugin</artifactId>
|
||||
<version>${io.spring.maven.antora-version}</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
</includes>
|
||||
<argLine>${surefireArgLine}</argLine>
|
||||
<options>
|
||||
<option>--to-dir=target/antora/site</option>
|
||||
<option>--stacktrace</option>
|
||||
<option>--fetch</option>
|
||||
</options>
|
||||
<environment>
|
||||
<ALGOLIA_API_KEY>9d489079e5ec46dbb238909fee5c9c29</ALGOLIA_API_KEY>
|
||||
<ALGOLIA_APP_ID>WB1FQYI187</ALGOLIA_APP_ID>
|
||||
<ALGOLIA_INDEX_NAME>springcloudtask</ALGOLIA_INDEX_NAME>
|
||||
</environment>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<testSource>17</testSource>
|
||||
<testTarget>17</testTarget>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<doclint>all,-missing</doclint>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>aggregate</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<!--
|
||||
Prepares the property pointing to the JaCoCo runtime agent which
|
||||
is passed as VM argument when Maven the Surefire plugin is executed.
|
||||
-->
|
||||
<execution>
|
||||
<id>pre-unit-test</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- Sets the path to the file which contains the execution data. -->
|
||||
<destFile>
|
||||
${project.build.directory}/coverage-reports/jacoco-ut.exec
|
||||
</destFile>
|
||||
<!--
|
||||
Sets the name of the property containing the settings
|
||||
for JaCoCo runtime agent.
|
||||
-->
|
||||
<propertyName>surefireArgLine</propertyName>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!--
|
||||
Ensures that the code coverage report for unit tests is created after
|
||||
unit tests have been run.
|
||||
-->
|
||||
<execution>
|
||||
<id>post-unit-test</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- Sets the path to the file which contains the execution data. -->
|
||||
<dataFile>
|
||||
${project.build.directory}/coverage-reports/jacoco-ut.exec
|
||||
</dataFile>
|
||||
<!-- Sets the output directory for the code coverage report. -->
|
||||
<outputDirectory>
|
||||
${project.reporting.outputDirectory}/jacoco-ut
|
||||
</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<version>0.8.7</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.spring.javaformat</groupId>
|
||||
<artifactId>spring-javaformat-maven-plugin</artifactId>
|
||||
<version>${spring-javaformat-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<inherited>true</inherited>
|
||||
<goals>
|
||||
<goal>validate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>withoutDockerTests</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludedGroups>DockerRequired</excludedGroups>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-task-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-starter-single-step-batch-job</artifactId>
|
||||
|
||||
<properties>
|
||||
<spring-cloud-commons.version>4.1.0-SNAPSHOT</spring-cloud-commons.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-batch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.batch</groupId>
|
||||
<artifactId>spring-batch-infrastructure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.batch</groupId>
|
||||
<artifactId>spring-batch-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-amqp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>rabbitmq</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure;
|
||||
|
||||
import org.springframework.batch.item.file.transform.Range;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* Converter for taking properties of format {@code start-end} or {@code start} (where
|
||||
* start and end are both integers) and converting them into {@link Range} instances for
|
||||
* configuring a {@link org.springframework.batch.item.file.FlatFileItemReader}.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
public class RangeConverter implements Converter<String, Range> {
|
||||
|
||||
@Override
|
||||
public Range convert(String source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] columns = source.split("-");
|
||||
|
||||
if (columns.length == 1) {
|
||||
int start = Integer.parseInt(columns[0]);
|
||||
return new Range(start);
|
||||
}
|
||||
else if (columns.length == 2) {
|
||||
int start = Integer.parseInt(columns[0]);
|
||||
int end = Integer.parseInt(columns[1]);
|
||||
return new Range(start, end);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(String
|
||||
.format("%s is in an illegal format. Ranges must be specified as startIndex-endIndex", source));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.SimpleStepBuilder;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Autoconfiguration to create a single step Spring Batch Job.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(SingleStepJobProperties.class)
|
||||
@AutoConfigureBefore(BatchAutoConfiguration.class)
|
||||
public class SingleStepJobAutoConfiguration {
|
||||
|
||||
private SingleStepJobProperties properties;
|
||||
|
||||
@Autowired
|
||||
PlatformTransactionManager transactionManager;
|
||||
|
||||
@Autowired
|
||||
JobRepository jobRepository;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ItemProcessor<Map<String, Object>, Map<String, Object>> itemProcessor;
|
||||
|
||||
public SingleStepJobAutoConfiguration(SingleStepJobProperties properties, ApplicationContext context) {
|
||||
|
||||
validateProperties(properties);
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
private void validateProperties(SingleStepJobProperties properties) {
|
||||
Assert.hasText(properties.getJobName(), "A job name is required");
|
||||
Assert.hasText(properties.getStepName(), "A step name is required");
|
||||
Assert.notNull(properties.getChunkSize(), "A chunk size is required");
|
||||
Assert.isTrue(properties.getChunkSize() > 0, "A chunk size greater than zero is required");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job", name = "job-name")
|
||||
public Job job(ItemReader<Map<String, Object>> itemReader, ItemWriter<Map<String, Object>> itemWriter) {
|
||||
|
||||
SimpleStepBuilder<Map<String, Object>, Map<String, Object>> stepBuilder = new StepBuilder(
|
||||
this.properties.getStepName(), this.jobRepository)
|
||||
.<Map<String, Object>, Map<String, Object>>chunk(this.properties.getChunkSize(),
|
||||
this.transactionManager)
|
||||
.reader(itemReader);
|
||||
|
||||
stepBuilder.processor(this.itemProcessor);
|
||||
|
||||
Step step = stepBuilder.writer(itemWriter).build();
|
||||
|
||||
return new JobBuilder(this.properties.getJobName(), this.jobRepository).start(step).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties to configure the step and job level properties for a single step job.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job")
|
||||
public class SingleStepJobProperties {
|
||||
|
||||
/**
|
||||
* Name of the step in the single step job.
|
||||
*/
|
||||
private String stepName;
|
||||
|
||||
/**
|
||||
* The number of items to process per transaction or chunk.
|
||||
*/
|
||||
private Integer chunkSize;
|
||||
|
||||
/**
|
||||
* The name of the job.
|
||||
*/
|
||||
private String jobName;
|
||||
|
||||
/**
|
||||
* Name of the step in the single step job.
|
||||
* @return name
|
||||
*/
|
||||
public String getStepName() {
|
||||
return stepName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the step.
|
||||
* @param stepName name
|
||||
*/
|
||||
public void setStepName(String stepName) {
|
||||
this.stepName = stepName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of items to process per transaction/chunk.
|
||||
* @return number of items
|
||||
*/
|
||||
public Integer getChunkSize() {
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of items within a transaction/chunk.
|
||||
* @param chunkSize number of items
|
||||
*/
|
||||
public void setChunkSize(Integer chunkSize) {
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the job.
|
||||
* @return name
|
||||
*/
|
||||
public String getJobName() {
|
||||
return jobName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the job.
|
||||
* @param jobName name
|
||||
*/
|
||||
public void setJobName(String jobName) {
|
||||
this.jobName = jobName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.flatfile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.item.file.FlatFileItemReader;
|
||||
import org.springframework.batch.item.file.LineCallbackHandler;
|
||||
import org.springframework.batch.item.file.LineMapper;
|
||||
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
|
||||
import org.springframework.batch.item.file.mapping.FieldSetMapper;
|
||||
import org.springframework.batch.item.file.separator.RecordSeparatorPolicy;
|
||||
import org.springframework.batch.item.file.transform.FieldSet;
|
||||
import org.springframework.batch.item.file.transform.LineTokenizer;
|
||||
import org.springframework.batch.item.file.transform.Range;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.RangeConverter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Autconfiguration for a {@code FlatFileItemReader}.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(FlatFileItemReaderProperties.class)
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
public class FlatFileItemReaderAutoConfiguration {
|
||||
|
||||
private final FlatFileItemReaderProperties properties;
|
||||
|
||||
public FlatFileItemReaderAutoConfiguration(FlatFileItemReaderProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.flatfileitemreader", name = "name")
|
||||
public FlatFileItemReader<Map<String, Object>> itemReader(@Autowired(required = false) LineTokenizer lineTokenizer,
|
||||
@Autowired(required = false) FieldSetMapper<Map<String, Object>> fieldSetMapper,
|
||||
@Autowired(required = false) LineMapper<Map<String, Object>> lineMapper,
|
||||
@Autowired(required = false) LineCallbackHandler skippedLinesCallback,
|
||||
@Autowired(required = false) RecordSeparatorPolicy recordSeparatorPolicy) {
|
||||
FlatFileItemReaderBuilder<Map<String, Object>> mapFlatFileItemReaderBuilder = new FlatFileItemReaderBuilder<Map<String, Object>>()
|
||||
.name(this.properties.getName()).resource(this.properties.getResource())
|
||||
.saveState(this.properties.isSaveState()).maxItemCount(this.properties.getMaxItemCount())
|
||||
.currentItemCount(this.properties.getCurrentItemCount()).strict(this.properties.isStrict())
|
||||
.encoding(this.properties.getEncoding()).linesToSkip(this.properties.getLinesToSkip())
|
||||
.comments(this.properties.getComments().toArray(new String[this.properties.getComments().size()]));
|
||||
|
||||
mapFlatFileItemReaderBuilder.lineTokenizer(lineTokenizer);
|
||||
if (recordSeparatorPolicy != null) {
|
||||
mapFlatFileItemReaderBuilder.recordSeparatorPolicy(recordSeparatorPolicy);
|
||||
}
|
||||
mapFlatFileItemReaderBuilder.fieldSetMapper(fieldSetMapper);
|
||||
mapFlatFileItemReaderBuilder.lineMapper(lineMapper);
|
||||
mapFlatFileItemReaderBuilder.skippedLinesCallback(skippedLinesCallback);
|
||||
|
||||
if (this.properties.isDelimited()) {
|
||||
mapFlatFileItemReaderBuilder.delimited().quoteCharacter(this.properties.getQuoteCharacter())
|
||||
.delimiter(this.properties.getDelimiter())
|
||||
.includedFields(this.properties.getIncludedFields().toArray(new Integer[0]))
|
||||
.names(this.properties.getNames()).beanMapperStrict(this.properties.isParsingStrict())
|
||||
.fieldSetMapper(new MapFieldSetMapper());
|
||||
}
|
||||
else if (this.properties.isFixedLength()) {
|
||||
RangeConverter rangeConverter = new RangeConverter();
|
||||
List<Range> ranges = new ArrayList<>();
|
||||
this.properties.getRanges().forEach(range -> ranges.add(rangeConverter.convert(range)));
|
||||
mapFlatFileItemReaderBuilder.fixedLength().columns(ranges.toArray(new Range[0]))
|
||||
.names(this.properties.getNames()).fieldSetMapper(new MapFieldSetMapper())
|
||||
.beanMapperStrict(this.properties.isParsingStrict());
|
||||
}
|
||||
|
||||
return mapFlatFileItemReaderBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link FieldSetMapper} that takes a {@code FieldSet} and returns the
|
||||
* {@code Map<String, Object>} of its contents.
|
||||
*/
|
||||
public static class MapFieldSetMapper implements FieldSetMapper<Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> mapFieldSet(FieldSet fieldSet) {
|
||||
return new HashMap<String, Object>((Map) fieldSet.getProperties());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.flatfile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.item.file.FlatFileItemReader;
|
||||
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
|
||||
import org.springframework.batch.item.file.transform.Range;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Properties to configure a {@code FlatFileItemReader}.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.flatfileitemreader")
|
||||
public class FlatFileItemReaderProperties {
|
||||
|
||||
/**
|
||||
* Determines whether the state of the reader is persisted. Default is {@code true}.
|
||||
*/
|
||||
private boolean saveState = true;
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}. Required if
|
||||
* {@link #setSaveState} is set to {@code true}.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Configure the maximum number of items to be read.
|
||||
*/
|
||||
private int maxItemCount = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Index for the current item. Also used on restarts to indicate where to start from.
|
||||
*/
|
||||
private int currentItemCount = 0;
|
||||
|
||||
/**
|
||||
* A list of {@code String} elements used to indicate which records are comments.
|
||||
*/
|
||||
private List<String> comments = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The {@link Resource} to be used as input.
|
||||
*/
|
||||
private Resource resource;
|
||||
|
||||
/**
|
||||
* Configure whether the reader should be in strict mode (require the input
|
||||
* {@link Resource} to exist).
|
||||
*/
|
||||
private boolean strict = true;
|
||||
|
||||
/**
|
||||
* Configure the encoding used by the reader to read the input source. The default
|
||||
* value is {@link FlatFileItemReader#DEFAULT_CHARSET}.
|
||||
*/
|
||||
private String encoding = FlatFileItemReader.DEFAULT_CHARSET;
|
||||
|
||||
/**
|
||||
* The number of lines to skip at the beginning of reading the file.
|
||||
*/
|
||||
private int linesToSkip = 0;
|
||||
|
||||
/**
|
||||
* Indicates that a {@link DelimitedLineTokenizer} should be used to parse each line.
|
||||
*/
|
||||
private boolean delimited = false;
|
||||
|
||||
/**
|
||||
* Define the delimiter for the file.
|
||||
*/
|
||||
private String delimiter = DelimitedLineTokenizer.DELIMITER_COMMA;
|
||||
|
||||
/**
|
||||
* Define the character used to quote fields.
|
||||
*/
|
||||
private char quoteCharacter = DelimitedLineTokenizer.DEFAULT_QUOTE_CHARACTER;
|
||||
|
||||
/**
|
||||
* A list of indices of the fields within a delimited file to be included.
|
||||
*/
|
||||
private List<Integer> includedFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Indicates that a
|
||||
* {@link org.springframework.batch.item.file.transform.FixedLengthTokenizer} should
|
||||
* be used to parse the records in the file.
|
||||
*/
|
||||
private boolean fixedLength = false;
|
||||
|
||||
/**
|
||||
* The column ranges to be used to parse a fixed width file.
|
||||
*/
|
||||
private List<String> ranges = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The names of the fields to be parsed from the file.
|
||||
*/
|
||||
private String[] names;
|
||||
|
||||
/**
|
||||
* Indicates whether the number of tokens must match the number of configured fields.
|
||||
*/
|
||||
private boolean parsingStrict = true;
|
||||
|
||||
/**
|
||||
* Returns the configured value of if the state of the reader will be persisted.
|
||||
* @return true if the state will be persisted
|
||||
*/
|
||||
public boolean isSaveState() {
|
||||
return this.saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* @param saveState defaults to true
|
||||
*/
|
||||
public void setSaveState(boolean saveState) {
|
||||
this.saveState = saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured value of the name used to calculate {@code ExecutionContext}
|
||||
* keys.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}. Required if
|
||||
* {@link #setSaveState} is set to true.
|
||||
* @param name name of the reader instance
|
||||
* @see org.springframework.batch.item.ItemStreamSupport#setName(String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of items to be read.
|
||||
* @return the configured number of items, defaults to Integer.MAX_VALUE
|
||||
*/
|
||||
public int getMaxItemCount() {
|
||||
return this.maxItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the max number of items to be read.
|
||||
* @param maxItemCount the max items to be read
|
||||
* @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int)
|
||||
*/
|
||||
public void setMaxItemCount(int maxItemCount) {
|
||||
this.maxItemCount = maxItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the index of the current item.
|
||||
* @return item index
|
||||
*/
|
||||
public int getCurrentItemCount() {
|
||||
return this.currentItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index for the current item. Also used on restarts to indicate where to start from.
|
||||
* @param currentItemCount current index
|
||||
* @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int)
|
||||
*/
|
||||
public void setCurrentItemCount(int currentItemCount) {
|
||||
this.currentItemCount = currentItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of {@code String} values used to indicate what records are comments.
|
||||
* @return list of comment indicators
|
||||
*/
|
||||
public List<String> getComments() {
|
||||
return this.comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a list of {@code String} elements used to indicate what records are comments.
|
||||
* @param comments strings used to indicate commented lines
|
||||
*/
|
||||
public void setComments(List<String> comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* The input file for the {@code FlatFileItemReader}.
|
||||
* @return a Resource
|
||||
*/
|
||||
public Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Resource} to be used as input.
|
||||
* @param resource the input to the reader.
|
||||
* @see FlatFileItemReader#setResource(Resource)
|
||||
*/
|
||||
public void setResource(Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a missing input file is considered an error.
|
||||
* @return true if the input file is required.
|
||||
*/
|
||||
public boolean isStrict() {
|
||||
return this.strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the reader should be in strict mode (require the input
|
||||
* {@link Resource} to exist).
|
||||
* @param strict true if the input file is required to exist.
|
||||
* @see FlatFileItemReader#setStrict(boolean)
|
||||
*/
|
||||
public void setStrict(boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoding for the input file. Defaults to
|
||||
* {@code FlatFileItemReader#DEFAULT_CHARSET}.
|
||||
* @return the configured encoding
|
||||
*/
|
||||
public String getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the encoding used by the reader to read the input source. Default value
|
||||
* is {@link FlatFileItemReader#DEFAULT_CHARSET}.
|
||||
* @param encoding to use to read the input source.
|
||||
* @see FlatFileItemReader#setEncoding(String)
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of lines to skip when reading the input file.
|
||||
* @return number of lines
|
||||
*/
|
||||
public int getLinesToSkip() {
|
||||
return this.linesToSkip;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to skip at the beginning of reading the file.
|
||||
* @param linesToSkip number of lines to be skipped.
|
||||
* @see FlatFileItemReader#setLinesToSkip(int)
|
||||
*/
|
||||
public void setLinesToSkip(int linesToSkip) {
|
||||
this.linesToSkip = linesToSkip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the input file is a delimited file or not.
|
||||
* @return true if the file is delimited
|
||||
*/
|
||||
public boolean isDelimited() {
|
||||
return this.delimited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a {@link DelimitedLineTokenizer} should be used to parse each line.
|
||||
* @param delimited true if the file is a delimited file
|
||||
*/
|
||||
public void setDelimited(boolean delimited) {
|
||||
this.delimited = delimited;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code String} used to divide the record into fields.
|
||||
* @return the delimiter
|
||||
*/
|
||||
public String getDelimiter() {
|
||||
return this.delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the delimiter for the file.
|
||||
* @param delimiter String used as a delimiter between fields.
|
||||
* @see DelimitedLineTokenizer#setDelimiter(String)
|
||||
*/
|
||||
public void setDelimiter(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* The char used to indicate that a field is quoted.
|
||||
* @return the quote char
|
||||
*/
|
||||
public char getQuoteCharacter() {
|
||||
return this.quoteCharacter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the character used to quote fields.
|
||||
* @param quoteCharacter char used to define quoted fields
|
||||
* @see DelimitedLineTokenizer#setQuoteCharacter(char)
|
||||
*/
|
||||
public void setQuoteCharacter(char quoteCharacter) {
|
||||
this.quoteCharacter = quoteCharacter;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code List} of indices indicating what fields to include.
|
||||
* @return list of indices
|
||||
*/
|
||||
public List<Integer> getIncludedFields() {
|
||||
return this.includedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of indices of the fields within a delimited file to be included.
|
||||
* @param includedFields indices of the fields
|
||||
* @see DelimitedLineTokenizer#setIncludedFields(int[])
|
||||
*/
|
||||
public void setIncludedFields(List<Integer> includedFields) {
|
||||
this.includedFields = includedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a file contains records with fixed length columns.
|
||||
* @return true if the file is parsed using column indices
|
||||
*/
|
||||
public boolean isFixedLength() {
|
||||
return this.fixedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a
|
||||
* {@link org.springframework.batch.item.file.transform.FixedLengthTokenizer} should
|
||||
* be used to parse the records in the file.
|
||||
* @param fixedLength true if the records should be tokenized by column index
|
||||
*/
|
||||
public void setFixedLength(boolean fixedLength) {
|
||||
this.fixedLength = fixedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* The column ranges to be used to parsed a fixed width file.
|
||||
* @return a list of {@link Range} instances
|
||||
*/
|
||||
public List<String> getRanges() {
|
||||
return this.ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Column ranges for each field.
|
||||
* @param ranges list of ranges in start-end format (end is optional)
|
||||
*/
|
||||
public void setRanges(List<String> ranges) {
|
||||
this.ranges = ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Names of each column.
|
||||
* @return names
|
||||
*/
|
||||
public String[] getNames() {
|
||||
return this.names;
|
||||
}
|
||||
|
||||
/**
|
||||
* The names of the fields to be parsed from the file.
|
||||
* @param names names of fields
|
||||
*/
|
||||
public void setNames(String[] names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the number of tokens must match the number of configured fields.
|
||||
* @return true if they must match
|
||||
*/
|
||||
public boolean isParsingStrict() {
|
||||
return this.parsingStrict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the number of tokens must match the number of configured fields.
|
||||
* @param parsingStrict true if they must match
|
||||
*/
|
||||
public void setParsingStrict(boolean parsingStrict) {
|
||||
this.parsingStrict = parsingStrict;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.flatfile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.item.file.FlatFileFooterCallback;
|
||||
import org.springframework.batch.item.file.FlatFileHeaderCallback;
|
||||
import org.springframework.batch.item.file.FlatFileItemWriter;
|
||||
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
|
||||
import org.springframework.batch.item.file.transform.FieldExtractor;
|
||||
import org.springframework.batch.item.file.transform.LineAggregator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.io.WritableResource;
|
||||
|
||||
/**
|
||||
* Autoconfiguration for a {@code FlatFileItemWriter}.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(FlatFileItemWriterProperties.class)
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
public class FlatFileItemWriterAutoConfiguration {
|
||||
|
||||
private FlatFileItemWriterProperties properties;
|
||||
|
||||
@Autowired(required = false)
|
||||
private LineAggregator<Map<String, Object>> lineAggregator;
|
||||
|
||||
@Autowired(required = false)
|
||||
private FieldExtractor<Map<String, Object>> fieldExtractor;
|
||||
|
||||
@Autowired(required = false)
|
||||
private FlatFileHeaderCallback headerCallback;
|
||||
|
||||
@Autowired(required = false)
|
||||
private FlatFileFooterCallback footerCallback;
|
||||
|
||||
public FlatFileItemWriterAutoConfiguration(FlatFileItemWriterProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.flatfileitemwriter", name = "name")
|
||||
public FlatFileItemWriter<Map<String, Object>> itemWriter() {
|
||||
|
||||
if (this.properties.isDelimited() && this.properties.isFormatted()) {
|
||||
throw new IllegalStateException("An output file must be either delimited or formatted or a custom "
|
||||
+ "LineAggregator must be provided. Your current configuration specifies both delimited and formatted");
|
||||
}
|
||||
else if ((this.properties.isFormatted() || this.properties.isDelimited()) && this.lineAggregator != null) {
|
||||
throw new IllegalStateException(
|
||||
"A LineAggregator must be configured if the " + "output is not formatted or delimited");
|
||||
}
|
||||
|
||||
FlatFileItemWriterBuilder<Map<String, Object>> builder = new FlatFileItemWriterBuilder<Map<String, Object>>()
|
||||
.name(this.properties.getName()).resource((WritableResource) this.properties.getResource())
|
||||
.append(this.properties.isAppend()).encoding(this.properties.getEncoding())
|
||||
.forceSync(this.properties.isForceSync()).lineSeparator(this.properties.getLineSeparator())
|
||||
.saveState(this.properties.isSaveState()).shouldDeleteIfEmpty(this.properties.isShouldDeleteIfEmpty())
|
||||
.shouldDeleteIfExists(this.properties.isShouldDeleteIfExists())
|
||||
.transactional(this.properties.isTransactional()).headerCallback(this.headerCallback)
|
||||
.footerCallback(this.footerCallback);
|
||||
|
||||
if (this.properties.isDelimited()) {
|
||||
FlatFileItemWriterBuilder.DelimitedBuilder<Map<String, Object>> delimitedBuilder = builder.delimited()
|
||||
.delimiter(this.properties.getDelimiter());
|
||||
|
||||
if (this.fieldExtractor != null) {
|
||||
delimitedBuilder.fieldExtractor(this.fieldExtractor);
|
||||
}
|
||||
else {
|
||||
delimitedBuilder.fieldExtractor(new MapFieldExtractor(this.properties.getNames()));
|
||||
}
|
||||
}
|
||||
else if (this.properties.isFormatted()) {
|
||||
FlatFileItemWriterBuilder.FormattedBuilder<Map<String, Object>> formattedBuilder = builder.formatted()
|
||||
.format(this.properties.getFormat()).locale(this.properties.getLocale())
|
||||
.maximumLength(this.properties.getMaximumLength())
|
||||
.minimumLength(this.properties.getMinimumLength());
|
||||
|
||||
if (this.fieldExtractor != null) {
|
||||
formattedBuilder.fieldExtractor(this.fieldExtractor);
|
||||
}
|
||||
else {
|
||||
formattedBuilder.fieldExtractor(new MapFieldExtractor(this.properties.getNames()));
|
||||
}
|
||||
}
|
||||
else if (this.lineAggregator != null) {
|
||||
builder.lineAggregator(this.lineAggregator);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code FieldExtractor} that converts a {@code Map<String, Object>} to the ordered
|
||||
* {@code Object[]} required to populate an output record.
|
||||
*/
|
||||
public static class MapFieldExtractor implements FieldExtractor<Map<String, Object>> {
|
||||
|
||||
private String[] names;
|
||||
|
||||
public MapFieldExtractor(String[] names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] extract(Map<String, Object> item) {
|
||||
|
||||
List<Object> fields = new ArrayList<>(item.size());
|
||||
|
||||
for (String name : this.names) {
|
||||
fields.add(item.get(name));
|
||||
}
|
||||
|
||||
return fields.toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.flatfile;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.batch.item.file.FlatFileItemWriter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Properties for configuring a {@code FlatFileItemWriter}.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.flatfileitemwriter")
|
||||
public class FlatFileItemWriterProperties {
|
||||
|
||||
/**
|
||||
* The {@link Resource} to be used as output.
|
||||
*/
|
||||
private Resource resource;
|
||||
|
||||
/**
|
||||
* Configure the use of the {@code DelimitedLineAggregator} to generate the output per
|
||||
* item. Default is {@code false}.
|
||||
*/
|
||||
private boolean delimited;
|
||||
|
||||
/**
|
||||
* Indicates to use a {@code FormatterLineAggregator} to generate the output per item.
|
||||
* Default is {@code false}.
|
||||
*/
|
||||
private boolean formatted;
|
||||
|
||||
/**
|
||||
* Configure the format the {@code FormatterLineAggregator} uses for each item.
|
||||
*/
|
||||
private String format;
|
||||
|
||||
/**
|
||||
* Configure the {@code Locale} to use when generating the output.
|
||||
*/
|
||||
private Locale locale = Locale.getDefault();
|
||||
|
||||
/**
|
||||
* Configure the maximum record length. If 0, the size is unbounded.
|
||||
*/
|
||||
private int maximumLength = 0;
|
||||
|
||||
/**
|
||||
* Configure the minimum record length.
|
||||
*/
|
||||
private int minimumLength = 0;
|
||||
|
||||
/**
|
||||
* Configure the {@code String} used to delimit the fields in the output file.
|
||||
*/
|
||||
private String delimiter = ",";
|
||||
|
||||
/**
|
||||
* File encoding for the output file. Defaults to
|
||||
* {@code FlatFileItemWriter.DEFAULT_CHARSET})
|
||||
*/
|
||||
private String encoding = FlatFileItemWriter.DEFAULT_CHARSET;
|
||||
|
||||
/**
|
||||
* A flag indicating that changes should be force-synced to disk on flush. Defaults to
|
||||
* {@code false}.
|
||||
*/
|
||||
private boolean forceSync = false;
|
||||
|
||||
/**
|
||||
* Names of the fields to be extracted into the output.
|
||||
*/
|
||||
private String[] names;
|
||||
|
||||
/**
|
||||
* Configure if the output file is found if it should be appended to. Defaults to
|
||||
* {@code false}.
|
||||
*/
|
||||
private boolean append = false;
|
||||
|
||||
/**
|
||||
* String used to separate lines in output. Defaults to the {@code System} property
|
||||
* {@code line.separator}.
|
||||
*/
|
||||
private String lineSeparator = FlatFileItemWriter.DEFAULT_LINE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}. Required if
|
||||
* {@link #setSaveState} is set to {@code true}.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Returns the configured value of whether the state of the reader is persisted.
|
||||
*/
|
||||
private boolean saveState = true;
|
||||
|
||||
/**
|
||||
* Indicates whether the output file should be deleted if no output was written to it.
|
||||
* Defaults to {@code false}.
|
||||
*/
|
||||
private boolean shouldDeleteIfEmpty = false;
|
||||
|
||||
/**
|
||||
* Indicates whether an existing output file should be deleted on startup. Defaults to
|
||||
* {@code true}.
|
||||
*/
|
||||
private boolean shouldDeleteIfExists = true;
|
||||
|
||||
/**
|
||||
* Indicates whether flushing the buffer should be delayed while a transaction is
|
||||
* active. Defaults to {@code true}.
|
||||
*/
|
||||
private boolean transactional = FlatFileItemWriter.DEFAULT_TRANSACTIONAL;
|
||||
|
||||
/**
|
||||
* Returns the configured value of if the state of the reader will be persisted.
|
||||
* @return true if the state will be persisted
|
||||
*/
|
||||
public boolean isSaveState() {
|
||||
return this.saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* @param saveState defaults to true
|
||||
*/
|
||||
public void setSaveState(boolean saveState) {
|
||||
this.saveState = saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured value of the name used to calculate {@code ExecutionContext}
|
||||
* keys.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}. Required if
|
||||
* {@link #setSaveState} is set to true.
|
||||
* @param name name of the reader instance
|
||||
* @see org.springframework.batch.item.ItemStreamSupport#setName(String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The output file for the {@code FlatFileItemWriter}.
|
||||
* @return a {@code Resource}
|
||||
*/
|
||||
public Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Resource} to be used as output.
|
||||
* @param resource the input to the reader.
|
||||
* @see FlatFileItemWriter#setResource
|
||||
*/
|
||||
public void setResource(Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates of the output will be delimited by a configured string (, by default).
|
||||
* @return true if the output file is a delimited file
|
||||
*/
|
||||
public boolean isDelimited() {
|
||||
return delimited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the use of the {@code DelimitedLineAggregator} to generate the output per
|
||||
* item.
|
||||
* @param delimited indicator if the file will be delimited or not
|
||||
*/
|
||||
public void setDelimited(boolean delimited) {
|
||||
this.delimited = delimited;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a file is delimited, this {@code String} will be used as the delimiter between
|
||||
* fields.
|
||||
* @return delimiter
|
||||
*/
|
||||
public String getDelimiter() {
|
||||
return delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@code String} used to delimit the fields in the output file.
|
||||
* @param delimiter {@code String} used to delimit the fields of the output file.
|
||||
*/
|
||||
public void setDelimiter(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Names of the fields to be extracted into the output.
|
||||
* @return An array of field names
|
||||
*/
|
||||
public String[] getNames() {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an ordered array of field names used to generate the output of a file.
|
||||
* @param names An array of field names
|
||||
*/
|
||||
public void setNames(String[] names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if an output file is found and should be added onto instead of
|
||||
* replaced/deleted. False by default.
|
||||
* @return appending indicator
|
||||
*/
|
||||
public boolean isAppend() {
|
||||
return append;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the output file is found if it should be appended to. Defaults to
|
||||
* false.
|
||||
* @param append true if the output file should be appended onto if found.
|
||||
*/
|
||||
public void setAppend(boolean append) {
|
||||
this.append = append;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the output file will use String formatting to generate the output.
|
||||
* @return true if the file will contain formatted records defaults to true
|
||||
*/
|
||||
public boolean isFormatted() {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates to use a {@code FormatterLineAggregator} to generate the output per item.
|
||||
* @param formatted true if the output should be formatted via the
|
||||
* {@code FormatterLineAggregator}
|
||||
*/
|
||||
public void setFormatted(boolean formatted) {
|
||||
this.formatted = formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* File encoding for the output file.
|
||||
* @return the configured encoding for the output file (Defaults to
|
||||
* {@code FlatFileItemWriter.DEFAULT_CHARSET})
|
||||
*/
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure encoding of the output file.
|
||||
* @param encoding output encoding
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* A flag indicating that changes should be force-synced to disk on flush. Defaults to
|
||||
* false.
|
||||
* @return The current instance of the builder.
|
||||
*/
|
||||
public boolean isForceSync() {
|
||||
return forceSync;
|
||||
}
|
||||
|
||||
/**
|
||||
* A flag indicating that changes should be force-synced to disk on flush. Defaults to
|
||||
* false.
|
||||
* @param forceSync value to set the flag to
|
||||
*/
|
||||
public void setForceSync(boolean forceSync) {
|
||||
this.forceSync = forceSync;
|
||||
}
|
||||
|
||||
/**
|
||||
* String used to separate lines in output. Defaults to the System property
|
||||
* line.separator.
|
||||
* @return the separator string
|
||||
*/
|
||||
public String getLineSeparator() {
|
||||
return lineSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@code String} used to separate each line.
|
||||
* @param lineSeparator defaults to System's line.separator property
|
||||
*/
|
||||
public void setLineSeparator(String lineSeparator) {
|
||||
this.lineSeparator = lineSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the output file should be deleted if no output was written to it.
|
||||
* Defaults to false.
|
||||
* @return true if a file that is empty at the end of the step should be deleted.
|
||||
*/
|
||||
public boolean isShouldDeleteIfEmpty() {
|
||||
return shouldDeleteIfEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if an empty output file should be deleted once the step is complete.
|
||||
* Defaults to false.
|
||||
* @param shouldDeleteIfEmpty true if the file should be deleted if no items have been
|
||||
* written to it.
|
||||
*/
|
||||
public void setShouldDeleteIfEmpty(boolean shouldDeleteIfEmpty) {
|
||||
this.shouldDeleteIfEmpty = shouldDeleteIfEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if an existing output file should be deleted on startup. Defaults to
|
||||
* true.
|
||||
* @return if an existing output file should be deleted.
|
||||
*/
|
||||
public boolean isShouldDeleteIfExists() {
|
||||
return shouldDeleteIfExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures if an existing output file should be deleted on the start of the step.
|
||||
* Defaults to true.
|
||||
* @param shouldDeleteIfExists if true and an output file of a previous run is found,
|
||||
* it will be deleted.
|
||||
*/
|
||||
public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
|
||||
this.shouldDeleteIfExists = shouldDeleteIfExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if flushing the buffer should be delayed while a transaction is active.
|
||||
* Defaults to true.
|
||||
* @return flag indicating if flushing should be delayed during a transaction
|
||||
*/
|
||||
public boolean isTransactional() {
|
||||
return transactional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if output should not be flushed to disk during an active transaction.
|
||||
* @param transactional defaults to true
|
||||
*/
|
||||
public void setTransactional(boolean transactional) {
|
||||
this.transactional = transactional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format used with the {@code FormatterLineAggregator}.
|
||||
* @return the format for each item's output.
|
||||
*/
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the format the {@code FormatterLineAggregator} will use for each item.
|
||||
* @param format the format for each item's output.
|
||||
*/
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code Locale} used when generating the output file.
|
||||
* @return configured {@code Locale}. Defaults to {@code Locale.getDefault()}
|
||||
*/
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@code Locale} to use when generating the output.
|
||||
* @param locale the configured {@code Locale}
|
||||
*/
|
||||
public void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* The longest a record is allowed to be. If 0, the maximum is unlimited.
|
||||
* @return the max record length allowed. Defaults to 0.
|
||||
*/
|
||||
public int getMaximumLength() {
|
||||
return maximumLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the maximum record length. If 0, the size is unbounded.
|
||||
* @param maximumLength the maximum record length allowed.
|
||||
*/
|
||||
public void setMaximumLength(int maximumLength) {
|
||||
this.maximumLength = maximumLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum record length.
|
||||
* @return the minimum record length allowed.
|
||||
*/
|
||||
public int getMinimumLength() {
|
||||
return minimumLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the minimum record length.
|
||||
* @param minimumLength the minimum record length.
|
||||
*/
|
||||
public void setMinimumLength(int minimumLength) {
|
||||
this.minimumLength = minimumLength;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.task.configuration.DefaultTaskConfigurer;
|
||||
import org.springframework.cloud.task.configuration.TaskConfigurer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* Establishes the default {@link DataSource} for the Task when creating a
|
||||
* {@link DataSource} for
|
||||
* {@link org.springframework.batch.item.database.JdbcCursorItemReader} or
|
||||
* {@link org.springframework.batch.item.database.JdbcBatchItemWriter}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 3.0
|
||||
*/
|
||||
class JDBCSingleStepDataSourceAutoConfiguration {
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public TaskConfigurer myTaskConfigurer(DataSource dataSource) {
|
||||
return new DefaultTaskConfigurer(dataSource);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbcsinglestep.datasource", name = "enable", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
@ConditionalOnMissingBean(name = "springDataSourceProperties")
|
||||
@Bean(name = "springDataSourceProperties")
|
||||
@ConfigurationProperties("spring.datasource")
|
||||
@Primary
|
||||
public DataSourceProperties springDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbcsinglestep.datasource", name = "enable", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
@Bean(name = "springDataSource")
|
||||
@Primary
|
||||
public DataSource dataSource(
|
||||
@Qualifier("springDataSourceProperties") DataSourceProperties springDataSourceProperties) {
|
||||
DataSource dataSource = springDataSourceProperties.initializeDataSourceBuilder().build();
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.item.database.ItemPreparedStatementSetter;
|
||||
import org.springframework.batch.item.database.ItemSqlParameterSourceProvider;
|
||||
import org.springframework.batch.item.database.JdbcBatchItemWriter;
|
||||
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Autconfiguration for a {@code JdbcBatchItemWriter}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(JdbcBatchItemWriterProperties.class)
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
@Import(JDBCSingleStepDataSourceAutoConfiguration.class)
|
||||
public class JdbcBatchItemWriterAutoConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JdbcBatchItemWriterAutoConfiguration.class);
|
||||
|
||||
@Autowired(required = false)
|
||||
private ItemPreparedStatementSetter itemPreparedStatementSetter;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ItemSqlParameterSourceProvider itemSqlParameterSourceProvider;
|
||||
|
||||
@Autowired
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
private JdbcBatchItemWriterProperties properties;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
public JdbcBatchItemWriterAutoConfiguration(DataSource dataSource, JdbcBatchItemWriterProperties properties) {
|
||||
this.dataSource = dataSource;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbcbatchitemwriter", name = "name")
|
||||
public JdbcBatchItemWriter<Map<String, Object>> itemWriter() {
|
||||
DataSource writerDataSource = this.dataSource;
|
||||
try {
|
||||
writerDataSource = this.applicationContext.getBean("jdbcBatchItemWriterSpringDataSource", DataSource.class);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.info("Using Default Data Source for the JdbcBatchItemWriter");
|
||||
}
|
||||
|
||||
JdbcBatchItemWriterBuilder<Map<String, Object>> jdbcBatchItemWriterBuilder = new JdbcBatchItemWriterBuilder<Map<String, Object>>()
|
||||
.dataSource(writerDataSource).sql(this.properties.getSql());
|
||||
if (this.itemPreparedStatementSetter != null) {
|
||||
jdbcBatchItemWriterBuilder.itemPreparedStatementSetter(this.itemPreparedStatementSetter);
|
||||
}
|
||||
else if (this.itemSqlParameterSourceProvider != null) {
|
||||
jdbcBatchItemWriterBuilder.itemSqlParameterSourceProvider(this.itemSqlParameterSourceProvider);
|
||||
}
|
||||
else {
|
||||
jdbcBatchItemWriterBuilder.columnMapped();
|
||||
}
|
||||
jdbcBatchItemWriterBuilder.assertUpdates(this.properties.isAssertUpdates());
|
||||
return jdbcBatchItemWriterBuilder.build();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbcbatchitemwriter.datasource", name = "enable",
|
||||
havingValue = "true")
|
||||
@Bean(name = "jdbcBatchItemWriterDataSourceProperties")
|
||||
@ConfigurationProperties("jdbcbatchitemwriter.datasource")
|
||||
public DataSourceProperties jdbcBatchItemWriterDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbcbatchitemwriter.datasource", name = "enable",
|
||||
havingValue = "true")
|
||||
@Bean(name = "jdbcBatchItemWriterSpringDataSource")
|
||||
public DataSource writerDataSource(
|
||||
@Qualifier("jdbcBatchItemWriterDataSourceProperties") DataSourceProperties writerDataSourceProperties) {
|
||||
DataSource result = writerDataSourceProperties.initializeDataSourceBuilder().build();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties to configure a {@code JdbcBatchItemWriter}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.jdbcbatchitemwriter")
|
||||
public class JdbcBatchItemWriterProperties {
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The SQL statement to be used to update the database.
|
||||
*/
|
||||
private String sql;
|
||||
|
||||
/**
|
||||
* If set to {@code true}, confirms that every insert results in the update of at
|
||||
* least one row in the database. Defaults to {@code true}.
|
||||
*/
|
||||
private boolean assertUpdates = true;
|
||||
|
||||
/**
|
||||
* @return The current sql statement used to update the database.
|
||||
*/
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sql statement to be used to update the database.
|
||||
* @param sql the sql statement to be used.
|
||||
*/
|
||||
public void setSql(String sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if returns true then each insert will be confirmed to have at least one
|
||||
* insert in the database.
|
||||
*/
|
||||
public boolean isAssertUpdates() {
|
||||
return assertUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to true, confirms that every insert results in the update of at least one
|
||||
* row in the database. Defaults to True
|
||||
*/
|
||||
public void setAssertUpdates(boolean assertUpdates) {
|
||||
this.assertUpdates = assertUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured value of the name used to calculate {@code ExecutionContext}
|
||||
* keys.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}.
|
||||
* @param name name of the writer instance
|
||||
* @see org.springframework.batch.item.ItemStreamSupport#setName(String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.item.database.JdbcCursorItemReader;
|
||||
import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.jdbc.core.PreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(JdbcCursorItemReaderProperties.class)
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbccursoritemreader", name = "name")
|
||||
@Import(JDBCSingleStepDataSourceAutoConfiguration.class)
|
||||
public class JdbcCursorItemReaderAutoConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JdbcCursorItemReaderAutoConfiguration.class);
|
||||
|
||||
@Autowired
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
private final JdbcCursorItemReaderProperties properties;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
public JdbcCursorItemReaderAutoConfiguration(JdbcCursorItemReaderProperties properties, DataSource dataSource) {
|
||||
this.properties = properties;
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public JdbcCursorItemReader<Map<String, Object>> itemReader(
|
||||
@Autowired(required = false) RowMapper<Map<String, Object>> rowMapper,
|
||||
@Autowired(required = false) PreparedStatementSetter preparedStatementSetter) {
|
||||
DataSource readerDataSource = this.dataSource;
|
||||
try {
|
||||
readerDataSource = this.applicationContext.getBean("jdbcCursorItemReaderSpringDataSource",
|
||||
DataSource.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.info("Using Default Data Source for the JdbcCursorItemReader");
|
||||
|
||||
}
|
||||
return new JdbcCursorItemReaderBuilder<Map<String, Object>>().name(this.properties.getName())
|
||||
.currentItemCount(this.properties.getCurrentItemCount()).dataSource(readerDataSource)
|
||||
.driverSupportsAbsolute(this.properties.isDriverSupportsAbsolute())
|
||||
.fetchSize(this.properties.getFetchSize()).ignoreWarnings(this.properties.isIgnoreWarnings())
|
||||
.maxItemCount(this.properties.getMaxItemCount()).maxRows(this.properties.getMaxRows())
|
||||
.queryTimeout(this.properties.getQueryTimeout()).saveState(this.properties.isSaveState())
|
||||
.sql(this.properties.getSql()).rowMapper(rowMapper).preparedStatementSetter(preparedStatementSetter)
|
||||
.verifyCursorPosition(this.properties.isVerifyCursorPosition())
|
||||
.useSharedExtendedConnection(this.properties.isUseSharedExtendedConnection()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RowMapper<Map<String, Object>> rowMapper() {
|
||||
return new MapRowMapper();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbccursoritemreader.datasource", name = "enable",
|
||||
havingValue = "true")
|
||||
@Bean(name = "jdbcCursorItemReaderDataSourceProperties")
|
||||
@ConfigurationProperties("jdbccursoritemreader.datasource")
|
||||
public DataSourceProperties jdbcCursorItemReaderDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.jdbccursoritemreader.datasource", name = "enable",
|
||||
havingValue = "true")
|
||||
@Bean(name = "jdbcCursorItemReaderSpringDataSource")
|
||||
public DataSource readerDataSource(
|
||||
@Qualifier("jdbcCursorItemReaderDataSourceProperties") DataSourceProperties readerDataSourceProperties) {
|
||||
DataSource result = readerDataSourceProperties.initializeDataSourceBuilder().build();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class MapRowMapper implements RowMapper<Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
Map<String, Object> item = new HashMap<>(rs.getMetaData().getColumnCount());
|
||||
|
||||
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
|
||||
item.put(rs.getMetaData().getColumnName(i), rs.getObject(i));
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.jdbccursoritemreader")
|
||||
public class JdbcCursorItemReaderProperties {
|
||||
|
||||
/**
|
||||
* Configure whether the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* Defaults to {@code true}.
|
||||
*/
|
||||
private boolean saveState = true;
|
||||
|
||||
/**
|
||||
* Returns the configured value of the name used to calculate {@code ExecutionContext}
|
||||
* keys.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Configure the maximum number of items to be read.
|
||||
*/
|
||||
private int maxItemCount = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Index for the current item. Also used on restarts to indicate where to start from.
|
||||
* Defaults to 0.
|
||||
*/
|
||||
private int currentItemCount = 0;
|
||||
|
||||
/**
|
||||
* The number of items to return each time the cursor fetches from the server.
|
||||
*/
|
||||
private int fetchSize;
|
||||
|
||||
/**
|
||||
* Sets the maximum number of rows to be read with this reader.
|
||||
*/
|
||||
private int maxRows;
|
||||
|
||||
/**
|
||||
* The time in milliseconds for the query to timeout.
|
||||
*/
|
||||
private int queryTimeout;
|
||||
|
||||
/**
|
||||
* Establishes whether SQL warnings should be ignored. Defaults to {@code false}.
|
||||
*/
|
||||
private boolean ignoreWarnings;
|
||||
|
||||
/**
|
||||
* Sets whether the cursor's position should be validated with each item read.
|
||||
* Defaults to {@code false}.
|
||||
*/
|
||||
private boolean verifyCursorPosition;
|
||||
|
||||
/**
|
||||
* Establishes {@code false} the driver supports absolute positioning of a cursor.
|
||||
* Defaults to {@code false}.
|
||||
*/
|
||||
private boolean driverSupportsAbsolute;
|
||||
|
||||
/**
|
||||
* Establishes whether the connection used for the cursor is being used by all other
|
||||
* processing and is, therefore, part of the same transaction. Defaults to
|
||||
* {@code false}
|
||||
*/
|
||||
private boolean useSharedExtendedConnection;
|
||||
|
||||
/**
|
||||
* The SQL query to be executed.
|
||||
*/
|
||||
private String sql;
|
||||
|
||||
/**
|
||||
* Returns the configured value of if the state of the reader will be persisted.
|
||||
* @return true if the state will be persisted
|
||||
*/
|
||||
public boolean isSaveState() {
|
||||
return this.saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* @param saveState defaults to true
|
||||
*/
|
||||
public void setSaveState(boolean saveState) {
|
||||
this.saveState = saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured value of the name used to calculate {@code ExecutionContext}
|
||||
* keys.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}. Required if
|
||||
* {@link #setSaveState} is set to true.
|
||||
* @param name name of the reader instance
|
||||
* @see org.springframework.batch.item.ItemStreamSupport#setName(String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of items to be read.
|
||||
* @return the configured number of items, defaults to Integer.MAX_VALUE
|
||||
*/
|
||||
public int getMaxItemCount() {
|
||||
return this.maxItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the max number of items to be read.
|
||||
* @param maxItemCount the max items to be read
|
||||
* @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int)
|
||||
*/
|
||||
public void setMaxItemCount(int maxItemCount) {
|
||||
this.maxItemCount = maxItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the index of the current item.
|
||||
* @return item index
|
||||
*/
|
||||
public int getCurrentItemCount() {
|
||||
return this.currentItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index for the current item. Also used on restarts to indicate where to start from.
|
||||
* @param currentItemCount current index
|
||||
* @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int)
|
||||
*/
|
||||
public void setCurrentItemCount(int currentItemCount) {
|
||||
this.currentItemCount = currentItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the number of items to return each time the cursor fetches from the
|
||||
* server.
|
||||
* @return fetch size
|
||||
*/
|
||||
public int getFetchSize() {
|
||||
return fetchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of items to return each time the cursor fetches from the server.
|
||||
* @param fetchSize the number of items
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#fetchSize(int)
|
||||
*/
|
||||
public void setFetchSize(int fetchSize) {
|
||||
this.fetchSize = fetchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the maximum number of rows to read with this reader.
|
||||
* @return maxiumum number of items
|
||||
*/
|
||||
public int getMaxRows() {
|
||||
return maxRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of rows to be read with this reader.
|
||||
* @param maxRows maximum number of items
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#maxRows(int)
|
||||
*/
|
||||
public void setMaxRows(int maxRows) {
|
||||
this.maxRows = maxRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the time in milliseconds for the query to timeout.
|
||||
* @return milliseconds for the timeout
|
||||
*/
|
||||
public int getQueryTimeout() {
|
||||
return queryTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time in milliseconds for the query to timeout.
|
||||
* @param queryTimeout milliseconds
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#queryTimeout(int)
|
||||
*/
|
||||
public void setQueryTimeout(int queryTimeout) {
|
||||
this.queryTimeout = queryTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides if SQL warnings should be ignored.
|
||||
* @return true if warnings should be ignored
|
||||
*/
|
||||
public boolean isIgnoreWarnings() {
|
||||
return ignoreWarnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if SQL warnings should be ignored.
|
||||
* @param ignoreWarnings indicator if the warnings should be ignored
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#ignoreWarnings(boolean)
|
||||
*/
|
||||
public void setIgnoreWarnings(boolean ignoreWarnings) {
|
||||
this.ignoreWarnings = ignoreWarnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the cursor's position should be validated with each item read (to
|
||||
* confirm that the RowMapper has not moved the cursor's location).
|
||||
* @return true if the position should be validated
|
||||
*/
|
||||
public boolean isVerifyCursorPosition() {
|
||||
return verifyCursorPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides if the cursor's position should be validated with each item read.
|
||||
* @param verifyCursorPosition true if the position should be validated
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#verifyCursorPosition(boolean)
|
||||
*/
|
||||
public void setVerifyCursorPosition(boolean verifyCursorPosition) {
|
||||
this.verifyCursorPosition = verifyCursorPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides if the driver supports absolute positioning of a cursor.
|
||||
* @return true if the driver supports absolute positioning
|
||||
*/
|
||||
public boolean isDriverSupportsAbsolute() {
|
||||
return driverSupportsAbsolute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the driver supports absolute positioning of a cursor.
|
||||
* @param driverSupportsAbsolute true if the driver supports absolute positioning
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#driverSupportsAbsolute(boolean)
|
||||
*/
|
||||
public void setDriverSupportsAbsolute(boolean driverSupportsAbsolute) {
|
||||
this.driverSupportsAbsolute = driverSupportsAbsolute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the connection used for the cursor is being used by all other
|
||||
* processing and is, therefore, part of the same transaction.
|
||||
* @return true if the connection is shared beyond this query
|
||||
*/
|
||||
public boolean isUseSharedExtendedConnection() {
|
||||
return useSharedExtendedConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the the connection used for the cursor is being used by all other
|
||||
* processing and is, therefore, part of the same transaction.
|
||||
* @param useSharedExtendedConnection true if the connection is shared beyond this
|
||||
* query
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#useSharedExtendedConnection(boolean)
|
||||
*/
|
||||
public void setUseSharedExtendedConnection(boolean useSharedExtendedConnection) {
|
||||
this.useSharedExtendedConnection = useSharedExtendedConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SQL query to be executed.
|
||||
* @return the SQL query
|
||||
*/
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SQL query to be executed.
|
||||
* @param sql the query
|
||||
* @see org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder#sql(String)
|
||||
*/
|
||||
public void setSql(String sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.kafka;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.item.kafka.KafkaItemReader;
|
||||
import org.springframework.batch.item.kafka.builder.KafkaItemReaderBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* AutoConfiguration for a {@code KafkaItemReader}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties({ KafkaProperties.class, KafkaItemReaderProperties.class })
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
public class KafkaItemReaderAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private KafkaProperties kafkaProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.kafkaitemreader", name = "name")
|
||||
public KafkaItemReader<Object, Map<String, Object>> kafkaItemReader(
|
||||
KafkaItemReaderProperties kafkaItemReaderProperties) {
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.putAll(this.kafkaProperties.getConsumer().buildProperties());
|
||||
validateProperties(kafkaItemReaderProperties);
|
||||
if (kafkaItemReaderProperties.getPartitions() == null
|
||||
|| kafkaItemReaderProperties.getPartitions().size() == 0) {
|
||||
kafkaItemReaderProperties.setPartitions(new ArrayList<>(1));
|
||||
kafkaItemReaderProperties.getPartitions().add(0);
|
||||
}
|
||||
return new KafkaItemReaderBuilder<Object, Map<String, Object>>()
|
||||
.partitions(kafkaItemReaderProperties.getPartitions()).consumerProperties(consumerProperties)
|
||||
.name(kafkaItemReaderProperties.getName())
|
||||
.pollTimeout(Duration.ofSeconds(kafkaItemReaderProperties.getPollTimeOutInSeconds()))
|
||||
.saveState(kafkaItemReaderProperties.isSaveState()).topic(kafkaItemReaderProperties.getTopic()).build();
|
||||
}
|
||||
|
||||
private void validateProperties(KafkaItemReaderProperties kafkaItemReaderProperties) {
|
||||
if (!StringUtils.hasText(kafkaItemReaderProperties.getName())) {
|
||||
throw new IllegalArgumentException("Name must not be empty or null");
|
||||
}
|
||||
if (!StringUtils.hasText(kafkaItemReaderProperties.getTopic())) {
|
||||
throw new IllegalArgumentException("Topic must not be empty or null");
|
||||
}
|
||||
if (!StringUtils.hasText(this.kafkaProperties.getConsumer().getGroupId())) {
|
||||
throw new IllegalArgumentException("GroupId must not be empty or null");
|
||||
}
|
||||
if (this.kafkaProperties.getBootstrapServers() == null
|
||||
|| this.kafkaProperties.getBootstrapServers().size() == 0) {
|
||||
throw new IllegalArgumentException("Bootstrap Servers must be configured");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.kafka;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties to configure a {@code KafkaItemReader}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.kafkaitemreader")
|
||||
public class KafkaItemReaderProperties {
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The topic name from which the messages is read.
|
||||
*/
|
||||
private String topic;
|
||||
|
||||
/**
|
||||
* A list of partitions to manually assign to the consumer. Defaults to a single entry
|
||||
* value of 1.
|
||||
*/
|
||||
private List<Integer> partitions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Establish the {@code pollTimeout} for the {@code poll()} operations. Defaults to 30
|
||||
* seconds.
|
||||
*/
|
||||
private long pollTimeOutInSeconds = 30L;
|
||||
|
||||
/**
|
||||
* Configure whether the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* Defaults to {@code true}.
|
||||
*/
|
||||
private boolean saveState = true;
|
||||
|
||||
/**
|
||||
* Returns the configured value of the name used to calculate {@code ExecutionContext}
|
||||
* keys.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name used to calculate the key within the
|
||||
* {@link org.springframework.batch.item.ExecutionContext}.
|
||||
* @param name name of the writer instance
|
||||
* @see org.springframework.batch.item.ItemStreamSupport#setName(String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the topic from which messages will be read.
|
||||
* @return the name of the topic.
|
||||
*/
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
/**
|
||||
* The topic name from which the messages will be read.
|
||||
* @param topic name of the topic
|
||||
*/
|
||||
public void setTopic(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of partitions to manually assign to the consumer. Defaults to a single entry
|
||||
* value of 1.
|
||||
* @return the list of partitions.
|
||||
*/
|
||||
public List<Integer> getPartitions() {
|
||||
return partitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of partitions to manually assign to the consumer. Defaults to a single entry
|
||||
* value of 1.
|
||||
* @param partitions list of partitions
|
||||
*/
|
||||
public void setPartitions(List<Integer> partitions) {
|
||||
this.partitions = partitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pollTimeout for the poll() operations. Defaults to 30 seconds.
|
||||
* @return long containing the poll timeout.
|
||||
*/
|
||||
public long getPollTimeOutInSeconds() {
|
||||
return pollTimeOutInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pollTimeout for the poll() operations. Defaults to 30 seconds.
|
||||
* @param pollTimeOutInSeconds the number of seconds to wait before timing out.
|
||||
*/
|
||||
public void setPollTimeOutInSeconds(long pollTimeOutInSeconds) {
|
||||
this.pollTimeOutInSeconds = pollTimeOutInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* Defaults to true.
|
||||
* @return current status of the saveState flag.
|
||||
*/
|
||||
public boolean isSaveState() {
|
||||
return saveState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if the state of the
|
||||
* {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within
|
||||
* the {@link org.springframework.batch.item.ExecutionContext} for restart purposes.
|
||||
* @param saveState true if state should be persisted. Defaults to true.
|
||||
*/
|
||||
public void setSaveState(boolean saveState) {
|
||||
this.saveState = saveState;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.kafka;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.item.kafka.KafkaItemWriter;
|
||||
import org.springframework.batch.item.kafka.builder.KafkaItemWriterBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.kafka.core.ProducerFactory;
|
||||
import org.springframework.kafka.support.serializer.JsonSerializer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
*
|
||||
* Autconfiguration for a {@code KafkaItemReader}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties({ KafkaProperties.class, KafkaItemWriterProperties.class })
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
public class KafkaItemWriterAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private KafkaProperties kafkaProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job.kafkaitemwriter", name = "topic")
|
||||
public KafkaItemWriter<Object, Map<String, Object>> kafkaItemWriter(
|
||||
KafkaItemWriterProperties kafkaItemWriterProperties,
|
||||
ProducerFactory<Object, Map<String, Object>> producerFactory,
|
||||
@Qualifier("batchItemKeyMapper") Converter<Map<String, Object>, Object> itemKeyMapper) {
|
||||
|
||||
validateProperties(kafkaItemWriterProperties);
|
||||
KafkaTemplate template = new KafkaTemplate(producerFactory);
|
||||
template.setDefaultTopic(kafkaItemWriterProperties.getTopic());
|
||||
return new KafkaItemWriterBuilder<Object, Map<String, Object>>().delete(kafkaItemWriterProperties.isDelete())
|
||||
.kafkaTemplate(template).itemKeyMapper(itemKeyMapper).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "batchItemKeyMapper")
|
||||
public Converter<Map<String, Object>, Object> batchItemKeyMapper() {
|
||||
return new Converter<Map<String, Object>, Object>() {
|
||||
@Override
|
||||
public Object convert(Map<String, Object> source) {
|
||||
return source;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ProducerFactory<Object, Map<String, Object>> producerFactory() {
|
||||
Map<String, Object> configs = new HashMap<>();
|
||||
configs.putAll(this.kafkaProperties.getProducer().buildProperties());
|
||||
return new DefaultKafkaProducerFactory<>(configs, null, new JsonSerializer<>());
|
||||
}
|
||||
|
||||
private void validateProperties(KafkaItemWriterProperties kafkaItemWriterProperties) {
|
||||
Assert.hasText(kafkaItemWriterProperties.getTopic(), "topic must not be empty or null");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.kafka;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties to configure a {@code KafkaItemWriter}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.kafkaitemwriter")
|
||||
public class KafkaItemWriterProperties {
|
||||
|
||||
/**
|
||||
* The topic name from which the messages are written.
|
||||
*/
|
||||
private String topic;
|
||||
|
||||
/**
|
||||
* Indicate whether the items being passed to the writer are all to be sent as delete
|
||||
* events to the topic. Defaults to {@code false}.
|
||||
*/
|
||||
private boolean delete;
|
||||
|
||||
/**
|
||||
* Returns the name of the topic from which messages will be written.
|
||||
* @return the name of the topic.
|
||||
*/
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
/**
|
||||
* The topic name from which the messages are written.
|
||||
* @param topic name of the topic
|
||||
*/
|
||||
public void setTopic(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the items being passed to the writer are all to be sent as delete
|
||||
* events to the topic. A delete event is made of a key with a null value. If set to
|
||||
* false (default), the items will be sent with provided value and key converter by
|
||||
* the itemKeyMapper. If set to true, the items will be sent with the key converter
|
||||
* from the value by the itemKeyMapper and a null value.
|
||||
* @return removal indicator.
|
||||
*/
|
||||
public boolean isDelete() {
|
||||
return delete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the items being passed to the writer are all to be sent as delete
|
||||
* events to the topic. A delete event is made of a key with a null value. If set to
|
||||
* false (default), the items will be sent with provided value and key converter by
|
||||
* the itemKeyMapper. If set to true, the items will be sent with the key converter
|
||||
* from the value by the itemKeyMapper and a null value.
|
||||
* @param delete removal indicator.
|
||||
*/
|
||||
public void setDelete(boolean delete) {
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.rabbit;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.batch.item.amqp.AmqpItemReader;
|
||||
import org.springframework.batch.item.amqp.builder.AmqpItemReaderBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Autconfiguration for a {@code AmqpItemReader}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(AmqpItemReaderProperties.class)
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "spring.batch.job.amqpitemreader.enabled", havingValue = "true", matchIfMissing = false)
|
||||
public class AmqpItemReaderAutoConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private RabbitProperties rabbitProperties;
|
||||
|
||||
@Bean
|
||||
public AmqpItemReaderProperties amqpItemReaderProperties() {
|
||||
return new AmqpItemReaderProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AmqpItemReader<Map<String, Object>> amqpItemReader(AmqpTemplate amqpTemplate,
|
||||
@Autowired(required = false) Class itemType) {
|
||||
AmqpItemReaderBuilder<Map<String, Object>> builder = new AmqpItemReaderBuilder<Map<String, Object>>()
|
||||
.amqpTemplate(amqpTemplate);
|
||||
if (itemType != null) {
|
||||
builder.itemType(itemType);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = "spring.batch.job.amqpitemreader.jsonConverterEnabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
@Bean
|
||||
public MessageConverter messageConverter() {
|
||||
return new Jackson2JsonMessageConverter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.rabbit;
|
||||
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties to configure a {@code AmqpItemReader}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.amqpitemreader")
|
||||
public class AmqpItemReaderProperties {
|
||||
|
||||
/**
|
||||
* Enables or disables the {@code AmqpItemReader}. Defaults to {@code false}.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Establishes whether the {@link Jackson2JsonMessageConverter} is to be used as a
|
||||
* message converter. Defaults to {@code true}.
|
||||
*/
|
||||
private boolean jsonConverterEnabled = true;
|
||||
|
||||
/**
|
||||
* The state of the enabled flag.
|
||||
* @return true if AmqpItemReader is enabled. Otherwise false.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the AmqpItemReader.
|
||||
* @param enabled if true then AmqpItemReader will be enabled. Defaults to false.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* States whether the {@link Jackson2JsonMessageConverter} is used as a message
|
||||
* converter.
|
||||
* @return true if enabled else false.
|
||||
*/
|
||||
public boolean isJsonConverterEnabled() {
|
||||
return jsonConverterEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes whether the {@link Jackson2JsonMessageConverter} is to be used as a
|
||||
* message converter.
|
||||
* @param jsonConverterEnabled true if it is to be enabled else false. Defaults to
|
||||
* true.
|
||||
*/
|
||||
public void setJsonConverterEnabled(boolean jsonConverterEnabled) {
|
||||
this.jsonConverterEnabled = jsonConverterEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.rabbit;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.batch.item.amqp.AmqpItemWriter;
|
||||
import org.springframework.batch.item.amqp.builder.AmqpItemWriterBuilder;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Autconfiguration for a {@code AmqpItemWriter}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Michael Minella
|
||||
* @since 2.3
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(AmqpItemWriterProperties.class)
|
||||
@AutoConfigureAfter(BatchAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "spring.batch.job.amqpitemwriter.enabled", havingValue = "true", matchIfMissing = false)
|
||||
public class AmqpItemWriterAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public AmqpItemWriter<Map<String, Object>> amqpItemWriter(AmqpTemplate amqpTemplate) {
|
||||
return new AmqpItemWriterBuilder<Map<String, Object>>().amqpTemplate(amqpTemplate).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AmqpItemWriterProperties amqpItemWriterProperties() {
|
||||
return new AmqpItemWriterProperties();
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = "spring.batch.job.amqpitemwriter.jsonConverterEnabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
@Bean
|
||||
public MessageConverter messageConverter() {
|
||||
return new Jackson2JsonMessageConverter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.rabbit;
|
||||
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.batch.job.amqpitemwriter")
|
||||
public class AmqpItemWriterProperties {
|
||||
|
||||
/**
|
||||
* Enables or disables the AmqpItemWriter. Defaults to {@code false}.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Establishes whether the {@link Jackson2JsonMessageConverter} is to be used as a
|
||||
* message converter. Defaults to {@code true}.
|
||||
*/
|
||||
private boolean jsonConverterEnabled = true;
|
||||
|
||||
/**
|
||||
* The state of the enabled flag.
|
||||
* @return {@code true} if {@code AmqpItemWriter} is enabled. Otherwise {@code false}.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the {@code AmqpItemWriter}.
|
||||
* @param enabled if {@code true} then {@code AmqpItemWriter} is enabled. Defaults to
|
||||
* {@code false}.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* States whether the {@link Jackson2JsonMessageConverter} is used as a message
|
||||
* converter.
|
||||
* @return true if enabled else false.
|
||||
*/
|
||||
public boolean isJsonConverterEnabled() {
|
||||
return jsonConverterEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes whether the {@link Jackson2JsonMessageConverter} is to be used as a
|
||||
* message converter.
|
||||
* @param jsonConverterEnabled true if it is to be enabled else false. Defaults to
|
||||
* true.
|
||||
*/
|
||||
public void setJsonConverterEnabled(boolean jsonConverterEnabled) {
|
||||
this.jsonConverterEnabled = jsonConverterEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "spring.batch.job",
|
||||
"type": "org.springframework.cloud.task.batch.autoconfigure.SingleStepJobProperties",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.SingleStepJobProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader",
|
||||
"type": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter",
|
||||
"type": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.batch.job.chunk-size",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.SingleStepJobProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.comments",
|
||||
"type": "java.util.List<java.lang.String>",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.current-item-count",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.delimited",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.delimiter",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.encoding",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.fixed-length",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.included-fields",
|
||||
"type": "java.util.List<java.lang.Integer>",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.lines-to-skip",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.max-item-count",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.name",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.names",
|
||||
"type": "java.lang.String[]",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.parsing-strict",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.quote-character",
|
||||
"type": "java.lang.Character",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.ranges",
|
||||
"type": "java.util.List<org.springframework.batch.item.file.transform.Range>",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.resource",
|
||||
"type": "org.springframework.core.io.Resource",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.save-state",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilereader.strict",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderProperties",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.append",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.delimited",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.delimiter",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": ","
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.encoding",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.force-sync",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.format",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.formatted",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.line-separator",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.locale",
|
||||
"type": "java.util.Locale",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.maximum-length",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.minimum-length",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.name",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.names",
|
||||
"type": "java.lang.String[]",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.resource",
|
||||
"type": "org.springframework.core.io.Resource",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.save-state",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.should-delete-if-empty",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.should-delete-if-exists",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.flatfilewriter.transactional",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.job-name",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.SingleStepJobProperties"
|
||||
},
|
||||
{
|
||||
"name": "spring.batch.job.step-name",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "org.springframework.cloud.task.batch.autoconfigure.SingleStepJobProperties"
|
||||
}
|
||||
],
|
||||
"hints": []
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemReaderAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.RangeConverter
|
||||
org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.flatfile.FlatFileItemWriterAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.jdbc.JdbcBatchItemWriterAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.jdbc.JdbcCursorItemReaderAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.rabbit.AmqpItemReaderAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.rabbit.AmqpItemWriterAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.kafka.KafkaItemReaderAutoConfiguration
|
||||
org.springframework.cloud.task.batch.autoconfigure.kafka.KafkaItemWriterAutoConfiguration
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.item.file.transform.Range;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class RangeConverterTests {
|
||||
|
||||
@Test
|
||||
public void testNullInput() {
|
||||
RangeConverter converter = new RangeConverter();
|
||||
|
||||
assertThat(converter.convert(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartValueOnly() {
|
||||
RangeConverter converter = new RangeConverter();
|
||||
|
||||
Range range = converter.convert("5");
|
||||
|
||||
assertThat(range.getMin()).isEqualTo(5);
|
||||
assertThat(range.getMax()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAndEndValue() {
|
||||
RangeConverter converter = new RangeConverter();
|
||||
|
||||
Range range = converter.convert("5-25");
|
||||
|
||||
assertThat(range.getMin()).isEqualTo(5);
|
||||
assertThat(range.getMax()).isEqualTo(25);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalValue() {
|
||||
RangeConverter converter = new RangeConverter();
|
||||
|
||||
assertThatExceptionOfType(NumberFormatException.class).isThrownBy(() -> {
|
||||
converter.convert("invalid");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooManyValues() {
|
||||
RangeConverter converter = new RangeConverter();
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> {
|
||||
converter.convert("1-2-3-4");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.item.support.ListItemWriter;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class SingleStepJobAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void testInvalidProperties() {
|
||||
SingleStepJobProperties properties = new SingleStepJobProperties();
|
||||
|
||||
try {
|
||||
new SingleStepJobAutoConfiguration(properties, null);
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
assertThat(iae.getMessage()).isEqualTo("A job name is required");
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail("wrong exception was thrown", t);
|
||||
}
|
||||
|
||||
properties.setJobName("job");
|
||||
|
||||
try {
|
||||
new SingleStepJobAutoConfiguration(properties, null);
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
assertThat(iae.getMessage()).isEqualTo("A step name is required");
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail("wrong exception was thrown", t);
|
||||
}
|
||||
|
||||
properties.setStepName("step");
|
||||
|
||||
try {
|
||||
new SingleStepJobAutoConfiguration(properties, null);
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
assertThat(iae.getMessage()).isEqualTo("A chunk size is required");
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail("wrong exception was thrown", t);
|
||||
}
|
||||
|
||||
properties.setChunkSize(-5);
|
||||
|
||||
try {
|
||||
new SingleStepJobAutoConfiguration(properties, null);
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
assertThat(iae.getMessage()).isEqualTo("A chunk size greater than zero is required");
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail("wrong exception was thrown", t);
|
||||
}
|
||||
|
||||
properties.setChunkSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleConfiguration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(SimpleConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5");
|
||||
|
||||
validateConfiguration(applicationContextRunner);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleConfigurationKabobStyle() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(SimpleConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.job-name=job", "spring.batch.job.step-name=step1",
|
||||
"spring.batch.job.chunk-size=5");
|
||||
|
||||
validateConfiguration(applicationContextRunner);
|
||||
}
|
||||
|
||||
private void validateConfiguration(ApplicationContextRunner applicationContextRunner) {
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(3);
|
||||
|
||||
assertThat(writtenItems.get(0).get("item")).isEqualTo("foo");
|
||||
assertThat(writtenItems.get(1).get("item")).isEqualTo("bar");
|
||||
assertThat(writtenItems.get(2).get("item")).isEqualTo("baz");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class SimpleConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,438 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.flatfile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.file.LineCallbackHandler;
|
||||
import org.springframework.batch.item.file.LineMapper;
|
||||
import org.springframework.batch.item.file.mapping.FieldSetMapper;
|
||||
import org.springframework.batch.item.file.separator.RecordSeparatorPolicy;
|
||||
import org.springframework.batch.item.file.transform.DefaultFieldSet;
|
||||
import org.springframework.batch.item.file.transform.LineTokenizer;
|
||||
import org.springframework.batch.item.support.ListItemWriter;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.RangeConverter;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class FlatFileItemReaderAutoConfigurationTests {
|
||||
|
||||
/**
|
||||
* Contents of the file to be read (included here because it's UTF-16).
|
||||
*
|
||||
* <pre>
|
||||
* 1@2@3@4@5@six
|
||||
* # This should be ignored
|
||||
* 7@8@9@10@11@twelve
|
||||
* $ So should this
|
||||
* 13@14@15@16@17@eighteen
|
||||
* 19@20@21@22@23@%twenty four%
|
||||
* 15@26@27@28@29@thirty
|
||||
* 31@32@33@34@35@thirty six
|
||||
* 37@38@39@40@41@forty two
|
||||
* 43@44@45@46@47@forty eight
|
||||
* 49@50@51@52@53@fifty four
|
||||
* 55@56@57@58@59@sixty
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
public void testFullDelimitedConfiguration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(JobConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemReaderAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemreader.savestate=true",
|
||||
"spring.batch.job.flatfileitemreader.name=fullDelimitedConfiguration",
|
||||
"spring.batch.job.flatfileitemreader.maxItemCount=5",
|
||||
"spring.batch.job.flatfileitemreader.currentItemCount=2",
|
||||
"spring.batch.job.flatfileitemreader.comments=#,$",
|
||||
"spring.batch.job.flatfileitemreader.resource=/testUTF16.csv",
|
||||
"spring.batch.job.flatfileitemreader.strict=true",
|
||||
"spring.batch.job.flatfileitemreader.encoding=UTF-16",
|
||||
"spring.batch.job.flatfileitemreader.linesToSkip=1",
|
||||
"spring.batch.job.flatfileitemreader.delimited=true",
|
||||
"spring.batch.job.flatfileitemreader.delimiter=@",
|
||||
"spring.batch.job.flatfileitemreader.quoteCharacter=%",
|
||||
"spring.batch.job.flatfileitemreader.includedFields=1,3,5",
|
||||
"spring.batch.job.flatfileitemreader.names=foo,bar,baz",
|
||||
"spring.batch.job.flatfileitemreader.parsingStrict=false");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(3);
|
||||
assertThat(((Map) writtenItems.get(0)).get("foo")).isEqualTo("20");
|
||||
assertThat(((Map) writtenItems.get(0)).get("bar")).isEqualTo("22");
|
||||
assertThat(((Map) writtenItems.get(0)).get("baz")).isEqualTo("twenty four");
|
||||
assertThat(((Map) writtenItems.get(1)).get("foo")).isEqualTo("26");
|
||||
assertThat(((Map) writtenItems.get(1)).get("bar")).isEqualTo("28");
|
||||
assertThat(((Map) writtenItems.get(1)).get("baz")).isEqualTo("thirty");
|
||||
assertThat(((Map) writtenItems.get(2)).get("foo")).isEqualTo("32");
|
||||
assertThat(((Map) writtenItems.get(2)).get("bar")).isEqualTo("34");
|
||||
assertThat(((Map) writtenItems.get(2)).get("baz")).isEqualTo("thirty six");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixedWidthConfiguration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(JobConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, FlatFileItemReaderAutoConfiguration.class,
|
||||
RangeConverter.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemreader.savestate=true",
|
||||
"spring.batch.job.flatfileitemreader.name=fixedWidthConfiguration",
|
||||
"spring.batch.job.flatfileitemreader.comments=#,$",
|
||||
"spring.batch.job.flatfileitemreader.resource=/test.txt",
|
||||
"spring.batch.job.flatfileitemreader.strict=true",
|
||||
"spring.batch.job.flatfileitemreader.fixedLength=true",
|
||||
"spring.batch.job.flatfileitemreader.ranges=3-4,7-8,11",
|
||||
"spring.batch.job.flatfileitemreader.names=foo,bar,baz",
|
||||
"spring.batch.job.flatfileitemreader.parsingStrict=false");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(6);
|
||||
assertThat(((Map) writtenItems.get(0)).get("foo")).isEqualTo("2");
|
||||
assertThat(((Map) writtenItems.get(0)).get("bar")).isEqualTo("4");
|
||||
assertThat(((Map) writtenItems.get(0)).get("baz")).isEqualTo("six");
|
||||
assertThat(((Map) writtenItems.get(1)).get("foo")).isEqualTo("8");
|
||||
assertThat(((Map) writtenItems.get(1)).get("bar")).isEqualTo("10");
|
||||
assertThat(((Map) writtenItems.get(1)).get("baz")).isEqualTo("twelve");
|
||||
assertThat(((Map) writtenItems.get(2)).get("foo")).isEqualTo("14");
|
||||
assertThat(((Map) writtenItems.get(2)).get("bar")).isEqualTo("16");
|
||||
assertThat(((Map) writtenItems.get(2)).get("baz")).isEqualTo("eighteen");
|
||||
assertThat(((Map) writtenItems.get(3)).get("foo")).isEqualTo("20");
|
||||
assertThat(((Map) writtenItems.get(3)).get("bar")).isEqualTo("22");
|
||||
assertThat(((Map) writtenItems.get(3)).get("baz")).isEqualTo("twenty four");
|
||||
assertThat(((Map) writtenItems.get(4)).get("foo")).isEqualTo("26");
|
||||
assertThat(((Map) writtenItems.get(4)).get("bar")).isEqualTo("28");
|
||||
assertThat(((Map) writtenItems.get(4)).get("baz")).isEqualTo("thirty");
|
||||
assertThat(((Map) writtenItems.get(5)).get("foo")).isEqualTo("32");
|
||||
assertThat(((Map) writtenItems.get(5)).get("bar")).isEqualTo("34");
|
||||
assertThat(((Map) writtenItems.get(5)).get("baz")).isEqualTo("thirty six");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomLineMapper() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomLineMapperConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemReaderAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.batch.job.flatfileitemreader.name=fixedWidthConfiguration",
|
||||
"spring.batch.job.flatfileitemreader.resource=/test.txt",
|
||||
"spring.batch.job.flatfileitemreader.strict=true");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(8);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This test requires an input file with an even number of records.
|
||||
*/
|
||||
@Test
|
||||
public void testCustomRecordSeparatorAndSkippedLines() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(RecordSeparatorAndSkippedLinesJobConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, FlatFileItemReaderAutoConfiguration.class,
|
||||
RangeConverter.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.batch.job.flatfileitemreader.name=fixedWidthConfiguration",
|
||||
"spring.batch.job.flatfileitemreader.resource=/test.txt",
|
||||
"spring.batch.job.flatfileitemreader.linesToSkip=2",
|
||||
"spring.batch.job.flatfileitemreader.fixedLength=true",
|
||||
"spring.batch.job.flatfileitemreader.ranges=3-4,7-8,11",
|
||||
"spring.batch.job.flatfileitemreader.names=foo,bar,baz",
|
||||
"spring.batch.job.flatfileitemreader.strict=true");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
ListLineCallbackHandler callbackHandler = context.getBean(ListLineCallbackHandler.class);
|
||||
|
||||
assertThat(callbackHandler.getLines().size()).isEqualTo(2);
|
||||
|
||||
List writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomMapping() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomMappingConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, FlatFileItemReaderAutoConfiguration.class,
|
||||
RangeConverter.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.batch.job.flatfileitemreader.name=fixedWidthConfiguration",
|
||||
"spring.batch.job.flatfileitemreader.resource=/test.txt",
|
||||
"spring.batch.job.flatfileitemreader.maxItemCount=1",
|
||||
"spring.batch.job.flatfileitemreader.strict=true");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(1);
|
||||
assertThat(writtenItems.get(0).get("one")).isEqualTo("1 2 3");
|
||||
assertThat(writtenItems.get(0).get("two")).isEqualTo("4 5 six");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class CustomMappingConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LineTokenizer lineTokenizer() {
|
||||
return line -> new DefaultFieldSet(new String[] { line.substring(0, 5), line.substring(6) },
|
||||
new String[] { "one", "two" });
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FieldSetMapper<Map<String, Object>> fieldSetMapper() {
|
||||
return fieldSet -> new HashMap<String, Object>((Map) fieldSet.getProperties());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class JobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class RecordSeparatorAndSkippedLinesJobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RecordSeparatorPolicy recordSeparatorPolicy() {
|
||||
return new RecordSeparatorPolicy() {
|
||||
@Override
|
||||
public boolean isEndOfRecord(String record) {
|
||||
|
||||
boolean endOfRecord = false;
|
||||
|
||||
int index = record.indexOf('\n');
|
||||
|
||||
if (index > 0 && record.length() > index + 1) {
|
||||
endOfRecord = true;
|
||||
}
|
||||
|
||||
return endOfRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postProcess(String record) {
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preProcess(String record) {
|
||||
return record + '\n';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LineCallbackHandler lineCallbackHandler() {
|
||||
return new ListLineCallbackHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class CustomLineMapperConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LineMapper<Map<String, Object>> lineMapper() {
|
||||
return (line, lineNumber) -> {
|
||||
Map<String, Object> item = new HashMap<>(1);
|
||||
|
||||
item.put("line", line);
|
||||
item.put("lineNumber", lineNumber);
|
||||
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ListLineCallbackHandler implements LineCallbackHandler {
|
||||
|
||||
private List<String> lines = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void handleLine(String line) {
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
public List<String> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,530 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.flatfile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.file.FlatFileFooterCallback;
|
||||
import org.springframework.batch.item.file.FlatFileHeaderCallback;
|
||||
import org.springframework.batch.item.file.FlatFileItemWriter;
|
||||
import org.springframework.batch.item.file.transform.FieldExtractor;
|
||||
import org.springframework.batch.item.file.transform.LineAggregator;
|
||||
import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class FlatFileItemWriterAutoConfigurationTests {
|
||||
|
||||
private File outputFile;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
this.outputFile = File.createTempFile("flatfile-config-test-output", ".tmp");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
this.outputFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidation() {
|
||||
|
||||
FlatFileItemWriterProperties properties = new FlatFileItemWriterProperties();
|
||||
|
||||
properties.setFormatted(true);
|
||||
properties.setDelimited(true);
|
||||
|
||||
FlatFileItemWriterAutoConfiguration configuration = new FlatFileItemWriterAutoConfiguration(properties);
|
||||
|
||||
try {
|
||||
configuration.itemWriter();
|
||||
fail("Exception should have been thrown when both formatted and delimited are selected");
|
||||
}
|
||||
catch (IllegalStateException ise) {
|
||||
assertThat(ise.getMessage()).isEqualTo("An output file must be either delimited or formatted or a custom "
|
||||
+ "LineAggregator must be provided. Your current configuration specifies both delimited and formatted");
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Incorrect exception thrown", e);
|
||||
}
|
||||
|
||||
properties.setFormatted(true);
|
||||
properties.setDelimited(false);
|
||||
|
||||
ReflectionTestUtils.setField(configuration, "lineAggregator", new PassThroughLineAggregator<>());
|
||||
|
||||
try {
|
||||
configuration.itemWriter();
|
||||
fail("Exception should have been thrown when a LineAggregator and one of the autocreated options are selected");
|
||||
}
|
||||
catch (IllegalStateException ise) {
|
||||
assertThat(ise.getMessage())
|
||||
.isEqualTo("A LineAggregator must be configured if the " + "output is not formatted or delimited");
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Incorrect exception thrown", e);
|
||||
}
|
||||
|
||||
properties.setFormatted(false);
|
||||
properties.setDelimited(true);
|
||||
|
||||
try {
|
||||
configuration.itemWriter();
|
||||
fail("Exception should have been thrown when a LineAggregator and one of the autocreated options are selected");
|
||||
}
|
||||
catch (IllegalStateException ise) {
|
||||
assertThat(ise.getMessage())
|
||||
.isEqualTo("A LineAggregator must be configured if the " + "output is not formatted or delimited");
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Incorrect exception thrown", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelimitedFileGeneration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(DelimitedJobConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemwriter.name=fooWriter",
|
||||
String.format("spring.batch.job.flatfileitemwriter.resource=file://%s",
|
||||
this.outputFile.getAbsolutePath()),
|
||||
"spring.batch.job.flatfileitemwriter.encoding=UTF-16",
|
||||
"spring.batch.job.flatfileitemwriter.saveState=false",
|
||||
"spring.batch.job.flatfileitemwriter.shouldDeleteIfEmpty=true",
|
||||
"spring.batch.job.flatfileitemwriter.delimited=true",
|
||||
"spring.batch.job.flatfileitemwriter.names=item",
|
||||
"spring.batch.job.flatfileitemwriter.append=true",
|
||||
"spring.batch.job.flatfileitemwriter.forceSync=true",
|
||||
"spring.batch.job.flatfileitemwriter.shouldDeleteIfExists=false",
|
||||
"spring.batch.job.flatfileitemwriter.transactional=false");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
FlatFileItemWriter writer = context.getBean(FlatFileItemWriter.class);
|
||||
|
||||
assertThat(Assertions.linesOf(this.outputFile, StandardCharsets.UTF_16).size()).isEqualTo(3);
|
||||
assertThat(Assertions.contentOf((new ClassPathResource("writerTestUTF16.txt")).getFile())
|
||||
.equals(new FileSystemResource(this.outputFile)));
|
||||
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "saveState")).isFalse();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "append")).isTrue();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "forceSync")).isTrue();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "shouldDeleteIfExists")).isFalse();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "transactional")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormattedFileGeneration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(FormattedJobConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=2", "spring.batch.job.flatfileitemwriter.name=fooWriter",
|
||||
String.format("spring.batch.job.flatfileitemwriter.resource=file://%s",
|
||||
this.outputFile.getAbsolutePath()),
|
||||
"spring.batch.job.flatfileitemwriter.encoding=UTF-8",
|
||||
"spring.batch.job.flatfileitemwriter.formatted=true",
|
||||
"spring.batch.job.flatfileitemwriter.names=item",
|
||||
"spring.batch.job.flatfileitemwriter.format=item = %s",
|
||||
"spring.batch.job.flatfileitemwriter.minimumLength=8",
|
||||
"spring.batch.job.flatfileitemwriter.maximumLength=10");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
assertThat(Assertions.linesOf(this.outputFile).size()).isEqualTo(2);
|
||||
|
||||
String results = FileCopyUtils
|
||||
.copyToString(new InputStreamReader(new FileSystemResource(this.outputFile).getInputStream()));
|
||||
assertThat(results).isEqualTo("item = foo\nitem = bar\n");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormattedFieldExtractorFileGeneration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(FormattedFieldExtractorJobConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemwriter.name=fooWriter",
|
||||
String.format("spring.batch.job.flatfileitemwriter.resource=file://%s",
|
||||
this.outputFile.getAbsolutePath()),
|
||||
"spring.batch.job.flatfileitemwriter.encoding=UTF-8",
|
||||
"spring.batch.job.flatfileitemwriter.formatted=true",
|
||||
"spring.batch.job.flatfileitemwriter.names=item",
|
||||
"spring.batch.job.flatfileitemwriter.format=item = %s");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
assertThat(Assertions.linesOf(this.outputFile).size()).isEqualTo(3);
|
||||
|
||||
String results = FileCopyUtils
|
||||
.copyToString(new InputStreamReader(new FileSystemResource(this.outputFile).getInputStream()));
|
||||
assertThat(results).isEqualTo("item = f\nitem = b\nitem = b\n");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFieldExtractorFileGeneration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(FieldExtractorConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemwriter.name=fooWriter",
|
||||
String.format("spring.batch.job.flatfileitemwriter.resource=file://%s",
|
||||
this.outputFile.getAbsolutePath()),
|
||||
"spring.batch.job.flatfileitemwriter.encoding=UTF-8",
|
||||
"spring.batch.job.flatfileitemwriter.delimited=true");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
assertThat(Assertions.linesOf(this.outputFile, StandardCharsets.UTF_8).size()).isEqualTo(3);
|
||||
|
||||
String results = FileCopyUtils
|
||||
.copyToString(new InputStreamReader(new FileSystemResource(this.outputFile).getInputStream()));
|
||||
assertThat(results).isEqualTo("f\nb\nb\n");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomLineAggregatorFileGeneration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(LineAggregatorConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemwriter.name=fooWriter",
|
||||
String.format("spring.batch.job.flatfileitemwriter.resource=file://%s",
|
||||
this.outputFile.getAbsolutePath()),
|
||||
"spring.batch.job.flatfileitemwriter.encoding=UTF-8");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
assertThat(Assertions.linesOf(this.outputFile, StandardCharsets.UTF_8).size()).isEqualTo(3);
|
||||
|
||||
String results = FileCopyUtils
|
||||
.copyToString(new InputStreamReader(new FileSystemResource(this.outputFile).getInputStream()));
|
||||
assertThat(results).isEqualTo("{item=foo}\n{item=bar}\n{item=baz}\n");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderFooterFileGeneration() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(HeaderFooterConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
FlatFileItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.flatfileitemwriter.name=fooWriter",
|
||||
String.format("spring.batch.job.flatfileitemwriter.resource=file://%s",
|
||||
this.outputFile.getAbsolutePath()),
|
||||
"spring.batch.job.flatfileitemwriter.encoding=UTF-8",
|
||||
"spring.batch.job.flatfileitemwriter.delimited=true",
|
||||
"spring.batch.job.flatfileitemwriter.names=item");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
assertThat(Assertions.linesOf(this.outputFile, StandardCharsets.UTF_8).size()).isEqualTo(5);
|
||||
|
||||
String results = FileCopyUtils
|
||||
.copyToString(new InputStreamReader(new FileSystemResource(this.outputFile).getInputStream()));
|
||||
assertThat(results).isEqualTo("header\nfoo\nbar\nbaz\nfooter");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DelimitedJobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class LineAggregatorConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LineAggregator<Map<String, Object>> lineAggregator() {
|
||||
return new PassThroughLineAggregator<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class HeaderFooterConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FlatFileHeaderCallback headerCallback() {
|
||||
return writer -> writer.append("header");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FlatFileFooterCallback footerCallback() {
|
||||
return writer -> writer.append("footer");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class FieldExtractorConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FieldExtractor<Map<String, Object>> lineAggregator() {
|
||||
return item -> {
|
||||
List<String> fields = new ArrayList<>(1);
|
||||
|
||||
fields.add(((String) item.get("item")).substring(0, 1));
|
||||
return fields.toArray();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class FormattedJobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "tooLong"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class FormattedFieldExtractorJobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FieldExtractor<Map<String, Object>> lineAggregator() {
|
||||
return item -> {
|
||||
List<String> fields = new ArrayList<>(1);
|
||||
|
||||
fields.add(((String) item.get("item")).substring(0, 1));
|
||||
return fields.toArray();
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item", "foo"));
|
||||
items.add(Collections.singletonMap("item", "bar"));
|
||||
items.add(Collections.singletonMap("item", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.h2.tools.Server;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.database.ItemPreparedStatementSetter;
|
||||
import org.springframework.batch.item.database.ItemSqlParameterSourceProvider;
|
||||
import org.springframework.batch.item.database.JdbcBatchItemWriter;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.SqlTypeValue;
|
||||
import org.springframework.jdbc.core.StatementCreatorUtils;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.util.TestSocketUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
public class JdbcBatchItemWriterAutoConfigurationTests {
|
||||
|
||||
private final static String DATASOURCE_URL;
|
||||
|
||||
private final static String DATASOURCE_USER_NAME = "SA";
|
||||
|
||||
private final static String DATASOURCE_USER_PASSWORD = "''";
|
||||
|
||||
private final static String DATASOURCE_DRIVER_CLASS_NAME = "org.h2.Driver";
|
||||
|
||||
private static int randomPort;
|
||||
|
||||
static {
|
||||
randomPort = TestSocketUtils.findAvailableTcpPort();
|
||||
DATASOURCE_URL = "jdbc:h2:tcp://localhost:" + randomPort + "/mem:dataflow;DB_CLOSE_DELAY=-1;"
|
||||
+ "DB_CLOSE_ON_EXIT=FALSE";
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void clearDB() {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(DATASOURCE_DRIVER_CLASS_NAME);
|
||||
dataSource.setUrl(DATASOURCE_URL);
|
||||
dataSource.setUsername(DATASOURCE_USER_NAME);
|
||||
dataSource.setPassword(DATASOURCE_USER_PASSWORD);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.execute("TRUNCATE TABLE item");
|
||||
jdbcTemplate.execute("DELETE FROM BATCH_STEP_EXECUTION_CONTEXT");
|
||||
jdbcTemplate.execute("DELETE FROM BATCH_STEP_EXECUTION");
|
||||
jdbcTemplate.execute("DELETE FROM BATCH_JOB_EXECUTION_PARAMS");
|
||||
jdbcTemplate.execute("DELETE FROM BATCH_JOB_EXECUTION_CONTEXT");
|
||||
jdbcTemplate.execute("DELETE FROM BATCH_JOB_EXECUTION");
|
||||
jdbcTemplate.execute("DELETE FROM BATCH_JOB_INSTANCE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class,
|
||||
JdbcBatchItemWriterAutoConfigurationTests.DelimitedJobConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcBatchItemWriterAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jdbcsinglestep.datasource.enable=false");
|
||||
applicationContextRunner = updatePropertiesForTest(applicationContextRunner);
|
||||
|
||||
runTest(applicationContextRunner, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseTestWithWriterDataSource() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class,
|
||||
JdbcBatchItemWriterAutoConfigurationTests.DelimitedJobConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcBatchItemWriterAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jdbcbatchitemwriter.datasource.enable=true",
|
||||
"spring.batch.job.jdbcsinglestep.datasource.enable=false", "spring.batch.job.jobName=job",
|
||||
"spring.batch.job.stepName=step1", "spring.batch.job.chunkSize=5",
|
||||
"spring.batch.job.jdbcbatchitemwriter.name=fooWriter",
|
||||
"spring.batch.job.jdbcbatchitemwriter.sql=INSERT INTO item (item_name) VALUES (:item_name)",
|
||||
"spring.batch.jdbc.initialize-schema=always",
|
||||
"jdbcbatchitemwriter.datasource.url=" + DATASOURCE_URL,
|
||||
"jdbcbatchitemwriter.datasource.username=" + DATASOURCE_USER_NAME,
|
||||
"jdbcbatchitemwriter.datasource.password=" + DATASOURCE_USER_PASSWORD,
|
||||
"jdbcbatchitemwriter.datasource.driverClassName=" + DATASOURCE_DRIVER_CLASS_NAME);
|
||||
|
||||
runTest(applicationContextRunner, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customSqlParameterSourceTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class,
|
||||
JdbcBatchItemWriterAutoConfigurationTests.DelimitedDifferentKeyNameJobConfiguration.class,
|
||||
CustomSqlParameterSourceProviderConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcBatchItemWriterAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jdbcsinglestep.datasource.enable=false");
|
||||
applicationContextRunner = updatePropertiesForTest(applicationContextRunner);
|
||||
|
||||
runTest(applicationContextRunner, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preparedStatementSetterTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class,
|
||||
JdbcBatchItemWriterAutoConfigurationTests.DelimitedJobConfiguration.class,
|
||||
ItemPreparedStatementSetterConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcBatchItemWriterAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jdbcsinglestep.datasource.enable=false");
|
||||
applicationContextRunner = updatePropertiesForTest(applicationContextRunner);
|
||||
runTest(applicationContextRunner, false);
|
||||
}
|
||||
|
||||
private ApplicationContextRunner updatePropertiesForTest(ApplicationContextRunner applicationContextRunner) {
|
||||
return applicationContextRunner.withPropertyValues("spring.batch.job.jobName=job",
|
||||
"spring.batch.job.stepName=step1", "spring.batch.job.chunkSize=5",
|
||||
"spring.batch.job.jdbcbatchitemwriter.name=fooWriter",
|
||||
"spring.batch.job.jdbcbatchitemwriter.sql=INSERT INTO item (item_name) VALUES (:item_name)",
|
||||
"spring.batch.jdbc.initialize-schema=always");
|
||||
}
|
||||
|
||||
private void validateResultAndBean(ApplicationContext context, boolean isWriterDataSourcePresent) {
|
||||
DataSource dataSource;
|
||||
try {
|
||||
dataSource = context.getBean(DataSource.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException nde) {
|
||||
dataSource = context.getBean("jdbcBatchItemWriterSpringDataSource", DataSource.class);
|
||||
}
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList("SELECT item_name FROM item ORDER BY item_name");
|
||||
assertThat(result.size()).isEqualTo(3);
|
||||
|
||||
assertThat(result.get(0).get("item_name")).isEqualTo("bar");
|
||||
assertThat(result.get(1).get("item_name")).isEqualTo("baz");
|
||||
assertThat(result.get(2).get("item_name")).isEqualTo("foo");
|
||||
|
||||
JdbcBatchItemWriter writer = context.getBean(JdbcBatchItemWriter.class);
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "assertUpdates")).isTrue();
|
||||
assertThat((Integer) ReflectionTestUtils.getField(writer, "parameterCount")).isEqualTo(1);
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(writer, "usingNamedParameters")).isTrue();
|
||||
if (!isWriterDataSourcePresent) {
|
||||
assertThatThrownBy(() -> context.getBean("jdbcBatchItemWriterSpringDataSource"))
|
||||
.isInstanceOf(NoSuchBeanDefinitionException.class)
|
||||
.hasMessageContaining("No bean named 'jdbcBatchItemWriterSpringDataSource' available");
|
||||
}
|
||||
else {
|
||||
assertThat(context.getBean("jdbcBatchItemWriterSpringDataSource")).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
private void runTest(ApplicationContextRunner applicationContextRunner, boolean isWriterDataSourcePresent) {
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
validateResultAndBean(context, isWriterDataSourcePresent);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class TaskLauncherConfiguration {
|
||||
|
||||
private static Server defaultServer;
|
||||
|
||||
@Bean
|
||||
public Server initH2TCPServer() {
|
||||
Server server = null;
|
||||
try {
|
||||
if (defaultServer == null) {
|
||||
server = Server.createTcpServer("-ifNotExists", "-tcp", "-tcpAllowOthers", "-tcpPort",
|
||||
String.valueOf(randomPort)).start();
|
||||
defaultServer = server;
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(DATASOURCE_DRIVER_CLASS_NAME);
|
||||
dataSource.setUrl(DATASOURCE_URL);
|
||||
dataSource.setUsername(DATASOURCE_USER_NAME);
|
||||
dataSource.setPassword(DATASOURCE_USER_PASSWORD);
|
||||
ClassPathResource setupResource = new ClassPathResource("schema-h2.sql");
|
||||
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator(setupResource);
|
||||
resourceDatabasePopulator.execute(dataSource);
|
||||
}
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return defaultServer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource(Server server) {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(DATASOURCE_DRIVER_CLASS_NAME);
|
||||
dataSource.setUrl(DATASOURCE_URL);
|
||||
dataSource.setUsername(DATASOURCE_USER_NAME);
|
||||
dataSource.setPassword(DATASOURCE_USER_PASSWORD);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DelimitedJobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item_name", "foo"));
|
||||
items.add(Collections.singletonMap("item_name", "bar"));
|
||||
items.add(Collections.singletonMap("item_name", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DelimitedDifferentKeyNameJobConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>(3);
|
||||
|
||||
items.add(Collections.singletonMap("item_foo", "foo"));
|
||||
items.add(Collections.singletonMap("item_foo", "bar"));
|
||||
items.add(Collections.singletonMap("item_foo", "baz"));
|
||||
|
||||
return new ListItemReader<>(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class CustomSqlParameterSourceProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
public ItemSqlParameterSourceProvider<Map<String, Object>> itemSqlParameterSourceProvider() {
|
||||
return item -> new MapSqlParameterSource(new HashMap<String, Object>() {
|
||||
{
|
||||
put("item_name", item.get("item_foo"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class ItemPreparedStatementSetterConfiguration {
|
||||
|
||||
@Bean
|
||||
public ItemPreparedStatementSetter itemPreparedStatementSetter() {
|
||||
return new ItemPreparedStatementSetter() {
|
||||
@Override
|
||||
public void setValues(Object item, PreparedStatement ps) throws SQLException {
|
||||
Map<String, Object> mapItem = (Map<String, Object>) item;
|
||||
StatementCreatorUtils.setParameterValue(ps, 1, SqlTypeValue.TYPE_UNKNOWN, mapItem.get("item_name"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.jdbc;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.h2.tools.Server;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.database.JdbcCursorItemReader;
|
||||
import org.springframework.batch.item.support.ListItemWriter;
|
||||
import org.springframework.batch.item.util.ExecutionContextUserSupport;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.util.TestSocketUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* @author Michael Minella
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class JdbcCursorItemReaderAutoConfigurationTests {
|
||||
|
||||
private final static String DATASOURCE_URL;
|
||||
|
||||
private final static String DATASOURCE_USER_NAME = "SA";
|
||||
|
||||
private final static String DATASOURCE_USER_PASSWORD = "''";
|
||||
|
||||
private final static String DATASOURCE_DRIVER_CLASS_NAME = "org.h2.Driver";
|
||||
|
||||
private static int randomPort;
|
||||
|
||||
static {
|
||||
randomPort = TestSocketUtils.findAvailableTcpPort();
|
||||
DATASOURCE_URL = "jdbc:h2:tcp://localhost:" + randomPort + "/mem:dataflow;DB_CLOSE_DELAY=-1;"
|
||||
+ "DB_CLOSE_ON_EXIT=FALSE";
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void clearDB() {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(DATASOURCE_DRIVER_CLASS_NAME);
|
||||
dataSource.setUrl(DATASOURCE_URL);
|
||||
dataSource.setUsername(DATASOURCE_USER_NAME);
|
||||
dataSource.setPassword(DATASOURCE_USER_PASSWORD);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.execute("TRUNCATE TABLE item");
|
||||
jdbcTemplate.execute("DROP TABLE BATCH_JOB_EXECUTION CASCADE");
|
||||
jdbcTemplate.execute("DROP TABLE BATCH_JOB_INSTANCE CASCADE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntegration() {
|
||||
ApplicationContextRunner applicationContextRunner = applicationContextRunner().withPropertyValues(
|
||||
"spring.batch.job.jobName=integrationJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.jdbccursoritemreader.name=fooReader",
|
||||
"spring.batch.job.jdbccursoritemreader.sql=select item_name from item",
|
||||
"spring.batch.jdbc.initialize-schema=always",
|
||||
"spring.batch.job.jdbcsinglestep.datasource.enable=false");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> items = context.getBean(ListItemWriter.class).getWrittenItems();
|
||||
|
||||
assertThat(items.size()).isEqualTo(3);
|
||||
assertThat(items.get(0).get("ITEM_NAME")).isEqualTo("foo");
|
||||
assertThat(items.get(1).get("ITEM_NAME")).isEqualTo("bar");
|
||||
assertThat(items.get(2).get("ITEM_NAME")).isEqualTo("baz");
|
||||
assertThatThrownBy(() -> context.getBean("readerSpringDataSource"))
|
||||
.isInstanceOf(NoSuchBeanDefinitionException.class)
|
||||
.hasMessageContaining("No bean named 'readerSpringDataSource' available");
|
||||
});
|
||||
}
|
||||
|
||||
private ApplicationContextRunner applicationContextRunner() {
|
||||
return new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class, BaseConfiguration.class).withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcCursorItemReaderAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntegrationReaderDataSourceEnabled() {
|
||||
ApplicationContextRunner applicationContextRunner = applicationContextRunner().withPropertyValues(
|
||||
"spring.batch.job.jobName=integrationReaderJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.jdbccursoritemreader.name=fooReader",
|
||||
"spring.batch.job.jdbccursoritemreader.sql=select item_name from item",
|
||||
"spring.batch.jdbc.initialize-schema=always", "spring.batch.job.jdbcsinglestep.datasource.enable=false",
|
||||
"spring.batch.job.jdbccursoritemreader.datasource.enable=true",
|
||||
"jdbccursoritemreader.datasource.url=" + DATASOURCE_URL,
|
||||
"jdbccursoritemreader.datasource.username=" + DATASOURCE_USER_NAME,
|
||||
"jdbccursoritemreader.datasource.password=" + DATASOURCE_USER_PASSWORD,
|
||||
"jdbccursoritemreader.datasource.driverClassName=" + DATASOURCE_DRIVER_CLASS_NAME);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> items = context.getBean(ListItemWriter.class).getWrittenItems();
|
||||
|
||||
assertThat(items.size()).isEqualTo(3);
|
||||
assertThat(items.get(0).get("ITEM_NAME")).isEqualTo("foo");
|
||||
assertThat(items.get(1).get("ITEM_NAME")).isEqualTo("bar");
|
||||
assertThat(items.get(2).get("ITEM_NAME")).isEqualTo("baz");
|
||||
assertThat(context.getBean("jdbcCursorItemReaderSpringDataSource")).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomRowMapper() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class, RowMapperConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcCursorItemReaderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=rowMapperJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.jdbccursoritemreader.name=fooReader",
|
||||
"spring.batch.job.jdbccursoritemreader.sql=select * from item",
|
||||
"spring.batch.jdbc.initialize-schema=always",
|
||||
"spring.batch.job.jdbcsinglestep.datasource.enable=false");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> items = context.getBean(ListItemWriter.class).getWrittenItems();
|
||||
|
||||
assertThat(items.size()).isEqualTo(3);
|
||||
assertThat(items.get(0).get("item")).isEqualTo("foo");
|
||||
assertThat(items.get(1).get("item")).isEqualTo("bar");
|
||||
assertThat(items.get(2).get("item")).isEqualTo("baz");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoseyScenario() {
|
||||
final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class, BaseConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcCursorItemReaderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=roseyJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.jdbccursoritemreader.saveState=false",
|
||||
"spring.batch.job.jdbccursoritemreader.name=fooReader",
|
||||
"spring.batch.job.jdbccursoritemreader.maxItemCount=15",
|
||||
"spring.batch.job.jdbccursoritemreader.currentItemCount=2",
|
||||
"spring.batch.job.jdbccursoritemreader.fetchSize=4",
|
||||
"spring.batch.job.jdbccursoritemreader.maxRows=6",
|
||||
"spring.batch.job.jdbccursoritemreader.queryTimeout=8",
|
||||
"spring.batch.job.jdbccursoritemreader.ignoreWarnings=true",
|
||||
"spring.batch.job.jdbccursoritemreader.verifyCursorPosition=true",
|
||||
"spring.batch.job.jdbccursoritemreader.driverSupportsAbsolute=true",
|
||||
"spring.batch.job.jdbccursoritemreader.useSharedExtendedConnection=true",
|
||||
"spring.batch.job.jdbccursoritemreader.sql=select * from foo");
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
|
||||
JdbcCursorItemReader<Map<String, Object>> itemReader = context.getBean(JdbcCursorItemReader.class);
|
||||
|
||||
validateBean(itemReader);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateBean(JdbcCursorItemReader itemReader) {
|
||||
assertThat(itemReader.getSql()).isEqualTo("select * from foo");
|
||||
assertThat(itemReader.getDataSource()).isNotNull();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(itemReader, "saveState")).isFalse();
|
||||
assertThat(ReflectionTestUtils.getField(
|
||||
(ExecutionContextUserSupport) ReflectionTestUtils.getField(itemReader, "executionContextUserSupport"),
|
||||
"name")).isEqualTo("fooReader");
|
||||
assertThat((Integer) ReflectionTestUtils.getField(itemReader, "maxItemCount")).isEqualTo(15);
|
||||
assertThat((Integer) ReflectionTestUtils.getField(itemReader, "currentItemCount")).isEqualTo(2);
|
||||
assertThat((Integer) ReflectionTestUtils.getField(itemReader, "fetchSize")).isEqualTo(4);
|
||||
assertThat((Integer) ReflectionTestUtils.getField(itemReader, "maxRows")).isEqualTo(6);
|
||||
assertThat((Integer) ReflectionTestUtils.getField(itemReader, "queryTimeout")).isEqualTo(8);
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(itemReader, "ignoreWarnings")).isTrue();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(itemReader, "verifyCursorPosition")).isTrue();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(itemReader, "driverSupportsAbsolute")).isTrue();
|
||||
assertThat((Boolean) ReflectionTestUtils.getField(itemReader, "useSharedExtendedConnection")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoName() {
|
||||
final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class, BaseConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcCursorItemReaderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=noNameJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5");
|
||||
|
||||
assertThatThrownBy(() -> {
|
||||
runTest(applicationContextRunner);
|
||||
}).isInstanceOf(IllegalStateException.class).hasMessageContaining("UnsatisfiedDependencyException");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSqlName() {
|
||||
final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskLauncherConfiguration.class, BaseConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, JdbcCursorItemReaderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.jdbccursoritemreader.name=fooReader");
|
||||
|
||||
assertThatThrownBy(() -> {
|
||||
runTest(applicationContextRunner);
|
||||
}).isInstanceOf(IllegalStateException.class).hasMessageContaining("UnsatisfiedDependencyException");
|
||||
}
|
||||
|
||||
private void runTest(ApplicationContextRunner applicationContextRunner) {
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AutoConfigureBefore({ JdbcCursorItemReaderAutoConfiguration.class,
|
||||
JDBCSingleStepDataSourceAutoConfiguration.class })
|
||||
@Configuration
|
||||
public static class TaskLauncherConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
private static Server defaultServer;
|
||||
|
||||
@Bean
|
||||
public Server initH2TCPServer() {
|
||||
Server server = null;
|
||||
try {
|
||||
if (defaultServer == null) {
|
||||
server = Server.createTcpServer("-ifNotExists", "-tcp", "-tcpAllowOthers", "-tcpPort",
|
||||
String.valueOf(randomPort)).start();
|
||||
defaultServer = server;
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(DATASOURCE_DRIVER_CLASS_NAME);
|
||||
dataSource.setUrl(DATASOURCE_URL);
|
||||
dataSource.setUsername(DATASOURCE_USER_NAME);
|
||||
dataSource.setPassword(DATASOURCE_USER_PASSWORD);
|
||||
ClassPathResource setupResource = new ClassPathResource("schema-h2.sql");
|
||||
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator(setupResource);
|
||||
resourceDatabasePopulator.setContinueOnError(true);
|
||||
resourceDatabasePopulator.execute(dataSource);
|
||||
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.execute("TRUNCATE TABLE item");
|
||||
jdbcTemplate.execute("INSERT INTO item VALUES ('foo')");
|
||||
jdbcTemplate.execute("INSERT INTO item VALUES ('bar')");
|
||||
jdbcTemplate.execute("INSERT INTO item VALUES ('baz')");
|
||||
}
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return defaultServer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource(Server server) {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(DATASOURCE_DRIVER_CLASS_NAME);
|
||||
dataSource.setUrl(DATASOURCE_URL);
|
||||
dataSource.setUsername(DATASOURCE_USER_NAME);
|
||||
dataSource.setPassword(DATASOURCE_USER_PASSWORD);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class RowMapperConfiguration {
|
||||
|
||||
@Bean
|
||||
public RowMapper<Map<String, Object>> rowMapper() {
|
||||
return (rs, rowNum) -> {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
|
||||
item.put("item", rs.getString("item_name"));
|
||||
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.kafka;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.kafka.clients.admin.NewTopic;
|
||||
import org.apache.kafka.clients.producer.Producer;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.support.ListItemWriter;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
|
||||
import org.springframework.kafka.support.serializer.JsonDeserializer;
|
||||
import org.springframework.kafka.support.serializer.JsonSerializer;
|
||||
import org.springframework.kafka.test.EmbeddedKafkaBroker;
|
||||
import org.springframework.kafka.test.context.EmbeddedKafka;
|
||||
import org.springframework.kafka.test.utils.KafkaTestUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@EmbeddedKafka(partitions = 1, topics = { "test" })
|
||||
public class KafkaItemReaderAutoConfigurationTests {
|
||||
|
||||
private static EmbeddedKafkaBroker embeddedKafkaBroker;
|
||||
|
||||
@BeforeAll
|
||||
public static void setupTest(EmbeddedKafkaBroker embeddedKafka) {
|
||||
embeddedKafkaBroker = embeddedKafka;
|
||||
embeddedKafka.addTopics(new NewTopic("topic1", 1, (short) 1), new NewTopic("topic2", 2, (short) 1),
|
||||
new NewTopic("topic3", 1, (short) 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBaseKafkaItemReader() {
|
||||
final String topicName = "topic1";
|
||||
populateSingleTopic(topicName);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomMappingConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
KafkaItemReaderAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.kafka.consumer.bootstrap-servers=" + embeddedKafkaBroker.getBrokersAsString(),
|
||||
"spring.kafka.consumer.group-id=1", "spring.batch.job.kafkaitemreader.name=kafkaItemReader",
|
||||
"spring.batch.job.kafkaitemreader.poll-time-out-in-seconds=2",
|
||||
"spring.batch.job.kafkaitemreader.topic=" + topicName,
|
||||
"spring.kafka.consumer.value-deserializer=" + JsonDeserializer.class.getName());
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> writtenItems = itemWriter.getWrittenItems();
|
||||
|
||||
assertThat(writtenItems.size()).isEqualTo(4);
|
||||
assertThat(writtenItems.get(0).get("first_name")).isEqualTo("jane");
|
||||
assertThat(writtenItems.get(1).get("first_name")).isEqualTo("john");
|
||||
assertThat(writtenItems.get(2).get("first_name")).isEqualTo("susan");
|
||||
assertThat(writtenItems.get(3).get("first_name")).isEqualTo("jim");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBaseKafkaItemReaderMultiplePartitions() {
|
||||
final String topicName = "topic2";
|
||||
populateSingleTopic(topicName);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomMappingConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
KafkaItemReaderAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.kafka.consumer.bootstrap-servers=" + embeddedKafkaBroker.getBrokersAsString(),
|
||||
"spring.kafka.consumer.group-id=1", "spring.batch.job.kafkaitemreader.name=kafkaItemReader",
|
||||
"spring.batch.job.kafkaitemreader.partitions=0,1",
|
||||
"spring.batch.job.kafkaitemreader.poll-time-out-in-seconds=2",
|
||||
"spring.batch.job.kafkaitemreader.topic=" + topicName,
|
||||
"spring.kafka.consumer.value-deserializer=" + JsonDeserializer.class.getName());
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
basicValidation(itemWriter);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBaseKafkaItemReaderPollTimeoutDefault() {
|
||||
final String topicName = "topic3";
|
||||
populateSingleTopic(topicName);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomMappingConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
KafkaItemReaderAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.kafka.consumer.bootstrap-servers=" + embeddedKafkaBroker.getBrokersAsString(),
|
||||
"spring.kafka.consumer.group-id=1", "spring.batch.job.kafkaitemreader.name=kafkaItemReader",
|
||||
"spring.batch.job.kafkaitemreader.topic=" + topicName,
|
||||
"spring.kafka.consumer.value-deserializer=" + JsonDeserializer.class.getName());
|
||||
Date startTime = new Date();
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
ListItemWriter itemWriter = context.getBean(ListItemWriter.class);
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
Date endTime = new Date();
|
||||
long seconds = (endTime.getTime() - startTime.getTime()) / 1000;
|
||||
assertThat(seconds).isGreaterThanOrEqualTo(30);
|
||||
basicValidation(itemWriter);
|
||||
});
|
||||
}
|
||||
|
||||
private void basicValidation(ListItemWriter itemWriter) {
|
||||
List<Map<String, Object>> writtenItems = itemWriter.getWrittenItems();
|
||||
assertThat(writtenItems.size()).isEqualTo(4);
|
||||
List<Object> results = new ArrayList<>();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
results.add(writtenItems.get(i).get("first_name"));
|
||||
}
|
||||
|
||||
assertThat(results).contains("jane", "john", "susan", "jim");
|
||||
}
|
||||
|
||||
private void populateSingleTopic(String topic) {
|
||||
Map<String, Object> configps = new HashMap<>(KafkaTestUtils.producerProps(embeddedKafkaBroker));
|
||||
Producer<String, Object> producer = new DefaultKafkaProducerFactory<>(configps, new StringSerializer(),
|
||||
new JsonSerializer<>()).createProducer();
|
||||
Map<String, Object> testMap = new HashMap<>();
|
||||
testMap.put("first_name", "jane");
|
||||
producer.send(new ProducerRecord<>(topic, "my-aggregate-id", testMap));
|
||||
testMap = new HashMap<>();
|
||||
testMap.put("first_name", "john");
|
||||
producer.send(new ProducerRecord<>(topic, "my-aggregate-id", testMap));
|
||||
testMap = new HashMap<>();
|
||||
testMap.put("first_name", "susan");
|
||||
producer.send(new ProducerRecord<>(topic, "my-aggregate-id", testMap));
|
||||
testMap = new HashMap<>();
|
||||
testMap.put("first_name", "jim");
|
||||
producer.send(new ProducerRecord<>(topic, "my-aggregate-id", testMap));
|
||||
producer.flush();
|
||||
producer.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class CustomMappingConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.kafka;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.kafka.clients.consumer.Consumer;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||
import org.springframework.kafka.support.serializer.JsonDeserializer;
|
||||
import org.springframework.kafka.test.EmbeddedKafkaBroker;
|
||||
import org.springframework.kafka.test.context.EmbeddedKafka;
|
||||
import org.springframework.kafka.test.utils.KafkaTestUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@EmbeddedKafka(partitions = 1, topics = { "topic1" })
|
||||
public class KafkaItemWriterTests {
|
||||
|
||||
private static EmbeddedKafkaBroker embeddedKafkaBroker;
|
||||
|
||||
@BeforeAll
|
||||
public static void setupTest(EmbeddedKafkaBroker embeddedKafka) {
|
||||
embeddedKafkaBroker = embeddedKafka;
|
||||
embeddedKafka.addTopics("topic2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBaseKafkaItemWriter() {
|
||||
final String topicName = "topic1";
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomMappingConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
KafkaItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=job", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5",
|
||||
"spring.kafka.producer.bootstrap-servers=" + embeddedKafkaBroker.getBrokersAsString(),
|
||||
"spring.kafka.producer.keySerializer=org.springframework.kafka.support.serializer.JsonSerializer",
|
||||
"spring.batch.job.kafkaitemwriter.topic=" + topicName);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
waitForTopicPopulation(context);
|
||||
validateResults(topicName);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateResults(String topicName) {
|
||||
Map<String, Object> configs = new HashMap<>(KafkaTestUtils.consumerProps("1", "false", embeddedKafkaBroker));
|
||||
Consumer<String, Object> consumer = new DefaultKafkaConsumerFactory<>(configs, new StringDeserializer(),
|
||||
new JsonDeserializer<>()).createConsumer();
|
||||
consumer.subscribe(singleton(topicName));
|
||||
|
||||
ConsumerRecords<String, Object> consumerRecords = KafkaTestUtils.getRecords(consumer);
|
||||
assertThat(consumerRecords.count()).isEqualTo(5);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
consumerRecords.forEach(cs -> {
|
||||
result.add((Map<String, Object>) cs.value());
|
||||
});
|
||||
List<String> firstNames = new ArrayList<>();
|
||||
result.forEach(s -> firstNames.add((String) s.get("first_name")));
|
||||
assertThat(firstNames.size()).isEqualTo(5);
|
||||
assertThat(firstNames).contains("Jane");
|
||||
assertThat(firstNames).contains("John");
|
||||
assertThat(firstNames).contains("Liz");
|
||||
assertThat(firstNames).contains("Cameron");
|
||||
assertThat(firstNames).contains("Judy");
|
||||
}
|
||||
|
||||
private void waitForTopicPopulation(ApplicationContext context) throws Exception {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
Job job = context.getBean(Job.class);
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class CustomMappingConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemReader<Map<String, Object>> itemReader() {
|
||||
List<Map<String, Object>> list = new ArrayList<>(5);
|
||||
addNameToReaderList(list, "Jane");
|
||||
addNameToReaderList(list, "John");
|
||||
addNameToReaderList(list, "Liz");
|
||||
addNameToReaderList(list, "Cameron");
|
||||
addNameToReaderList(list, "Judy");
|
||||
return new ListItemReader<>(list);
|
||||
}
|
||||
|
||||
private void addNameToReaderList(List<Map<String, Object>> itemReaderList, String value) {
|
||||
Map<String, Object> prepMap = new HashMap<>();
|
||||
prepMap.put("first_name", value);
|
||||
itemReaderList.add(prepMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.rabbit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.RabbitMQContainer;
|
||||
|
||||
import org.springframework.amqp.core.AmqpAdmin;
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.support.ListItemWriter;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Tag("DockerRequired")
|
||||
public class AmqpItemReaderAutoConfigurationTests {
|
||||
|
||||
private static int amqpPort;
|
||||
|
||||
private static String host;
|
||||
|
||||
private RabbitTemplate template;
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
static {
|
||||
GenericContainer rabbitmq = new RabbitMQContainer("rabbitmq:3.8.9").withExposedPorts(5672);
|
||||
rabbitmq.start();
|
||||
final Integer mappedPort = rabbitmq.getMappedPort(5672);
|
||||
host = rabbitmq.getHost();
|
||||
amqpPort = mappedPort;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setupTest() {
|
||||
this.connectionFactory = new CachingConnectionFactory(host, amqpPort);
|
||||
this.template = new RabbitTemplate(this.connectionFactory);
|
||||
this.template.setMessageConverter(new Jackson2JsonMessageConverter());
|
||||
AmqpAdmin admin = new RabbitAdmin(this.connectionFactory);
|
||||
admin.declareQueue(new Queue("foo"));
|
||||
|
||||
Map<String, Object> testMap = new HashMap<>();
|
||||
testMap.put("ITEM_NAME", "foo");
|
||||
this.template.convertAndSend("foo", testMap);
|
||||
testMap = new HashMap<>();
|
||||
testMap.put("ITEM_NAME", "bar");
|
||||
this.template.convertAndSend("foo", testMap);
|
||||
testMap = new HashMap<>();
|
||||
testMap.put("ITEM_NAME", "baz");
|
||||
this.template.convertAndSend("foo", testMap);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void teardownTest() {
|
||||
AmqpAdmin admin = new RabbitAdmin(this.connectionFactory);
|
||||
admin.deleteQueue("foo");
|
||||
this.template.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(BaseConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, AmqpItemReaderAutoConfiguration.class,
|
||||
RabbitAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=integrationJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.amqpitemreader.enabled=true",
|
||||
"spring.rabbitmq.template.default-receive-queue=foo", "spring.rabbitmq.host=" + host,
|
||||
"spring.rabbitmq.port=" + amqpPort);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobExecution jobExecution = runJob(context);
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
validateBasicTest(context.getBean(ListItemWriter.class).getWrittenItems());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicTestWithItemType() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(ItemTypeConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, AmqpItemReaderAutoConfiguration.class,
|
||||
RabbitAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=integrationJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.amqpitemreader.enabled=true",
|
||||
"spring.rabbitmq.template.default-receive-queue=foo", "spring.rabbitmq.host=" + host,
|
||||
"spring.rabbitmq.port=" + amqpPort);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobExecution jobExecution = runJob(context);
|
||||
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
validateBasicTest(context.getBean(ListItemWriter.class).getWrittenItems());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void useAmqpTemplateTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(MockTemplateConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
AmqpItemReaderAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues("spring.batch.job.jobName=integrationJob", "spring.batch.job.stepName=step1",
|
||||
"spring.batch.job.chunkSize=5", "spring.batch.job.amqpitemreader.enabled=true",
|
||||
"spring.rabbitmq.host=" + host, "spring.rabbitmq.port=" + amqpPort);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
runJob(context);
|
||||
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
|
||||
Mockito.verify(amqpTemplate, Mockito.times(1)).receiveAndConvert();
|
||||
});
|
||||
}
|
||||
|
||||
private JobExecution runJob(AssertableApplicationContext context) throws Exception {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
Job job = context.getBean(Job.class);
|
||||
return jobLauncher.run(job, new JobParameters());
|
||||
}
|
||||
|
||||
private void validateBasicTest(List<Map<String, Object>> items) {
|
||||
assertThat(items.size()).isEqualTo(3);
|
||||
assertThat(items.get(0).get("ITEM_NAME")).isEqualTo("foo");
|
||||
assertThat(items.get(1).get("ITEM_NAME")).isEqualTo("bar");
|
||||
assertThat(items.get(2).get("ITEM_NAME")).isEqualTo("baz");
|
||||
}
|
||||
|
||||
public static class MockTemplateConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
AmqpTemplate amqpTemplateBean() {
|
||||
return Mockito.mock(AmqpTemplate.class);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public static class ItemTypeConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
Class<?> itemTypeClass() {
|
||||
return Map.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ListItemWriter<Map<String, Object>> itemWriter() {
|
||||
return new ListItemWriter<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.autoconfigure.rabbit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.RabbitMQContainer;
|
||||
|
||||
import org.springframework.amqp.core.AmqpAdmin;
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.batch.autoconfigure.SingleStepJobAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Tag("DockerRequired")
|
||||
public class AmqpItemWriterAutoConfigurationTests {
|
||||
|
||||
private final static String QUEUE_NAME = "foo";
|
||||
|
||||
private final static String EXCHANGE_NAME = "fooexchange";
|
||||
|
||||
private static int amqpPort;
|
||||
|
||||
private static String host;
|
||||
|
||||
private static List<Map<String, Object>> sampleData;
|
||||
|
||||
private RabbitTemplate template;
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
private String[] configurations;
|
||||
|
||||
static {
|
||||
GenericContainer rabbitmq = new RabbitMQContainer("rabbitmq:3.8.9").withExposedPorts(5672);
|
||||
rabbitmq.start();
|
||||
final Integer mappedPort = rabbitmq.getMappedPort(5672);
|
||||
host = rabbitmq.getHost();
|
||||
amqpPort = mappedPort;
|
||||
sampleData = new ArrayList<>(5);
|
||||
addNameToReaderList(sampleData, "Jane");
|
||||
addNameToReaderList(sampleData, "John");
|
||||
addNameToReaderList(sampleData, "Liz");
|
||||
addNameToReaderList(sampleData, "Cameron");
|
||||
addNameToReaderList(sampleData, "Judy");
|
||||
}
|
||||
|
||||
private static void addNameToReaderList(List<Map<String, Object>> itemReaderList, String value) {
|
||||
Map<String, Object> prepMap = new HashMap<>();
|
||||
prepMap.put("first_name", value);
|
||||
itemReaderList.add(prepMap);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setupTest() {
|
||||
this.connectionFactory = new CachingConnectionFactory(host, amqpPort);
|
||||
this.template = new RabbitTemplate(this.connectionFactory);
|
||||
this.template.setMessageConverter(new Jackson2JsonMessageConverter());
|
||||
AmqpAdmin admin = new RabbitAdmin(this.connectionFactory);
|
||||
admin.declareQueue(new Queue(QUEUE_NAME));
|
||||
admin.declareExchange(new TopicExchange(EXCHANGE_NAME));
|
||||
admin.declareBinding(new Binding(QUEUE_NAME, Binding.DestinationType.QUEUE, EXCHANGE_NAME, "#", null));
|
||||
this.configurations = new String[] { "spring.batch.job.jobName=integrationJob",
|
||||
"spring.batch.job.stepName=step1", "spring.batch.job.chunkSize=5",
|
||||
"spring.rabbitmq.template.exchange=" + EXCHANGE_NAME, "spring.rabbitmq.host=" + host,
|
||||
"spring.batch.job.amqpitemwriter.enabled=true", "spring.rabbitmq.port=" + amqpPort };
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void teardownTest() {
|
||||
AmqpAdmin admin = new RabbitAdmin(this.connectionFactory);
|
||||
admin.deleteQueue(QUEUE_NAME);
|
||||
this.template.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(BaseConfiguration.class)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class,
|
||||
SingleStepJobAutoConfiguration.class, AmqpItemWriterAutoConfiguration.class,
|
||||
RabbitAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues(this.configurations);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
JobExecution jobExecution = runJob(context);
|
||||
JobExplorer jobExplorer = context.getBean(JobExplorer.class);
|
||||
|
||||
while (jobExplorer.getJobExecution(jobExecution.getJobId()).isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
for (Map<String, Object> sampleEntry : sampleData) {
|
||||
Map<String, Object> map = (Map<String, Object>) template.receiveAndConvert(QUEUE_NAME);
|
||||
assertThat(map.get("first_name")).isEqualTo(sampleEntry.get("first_name"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void useAmqpTemplateTest() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(MockConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
|
||||
BatchAutoConfiguration.class, SingleStepJobAutoConfiguration.class,
|
||||
AmqpItemWriterAutoConfiguration.class, DataSourceAutoConfiguration.class))
|
||||
.withPropertyValues(this.configurations);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
runJob(context);
|
||||
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
|
||||
Mockito.verify(amqpTemplate, Mockito.times(5)).convertAndSend(Mockito.any());
|
||||
});
|
||||
}
|
||||
|
||||
private JobExecution runJob(AssertableApplicationContext context) throws Exception {
|
||||
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
|
||||
|
||||
Job job = context.getBean(Job.class);
|
||||
|
||||
return jobLauncher.run(job, new JobParameters());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class BaseConfiguration extends ItemWriterConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class MockConfiguration extends ItemWriterConfiguration {
|
||||
|
||||
@Bean
|
||||
AmqpTemplate amqpTemplateBean() {
|
||||
return Mockito.mock(AmqpTemplate.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ItemWriterConfiguration {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager platformTransactionManager() {
|
||||
return new ResourcelessTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RowMapper<Map<String, Object>> rowMapper() {
|
||||
return (rs, rowNum) -> {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
|
||||
item.put("item", rs.getString("item_name"));
|
||||
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ItemReader<Map<String, Object>> itemWriter() {
|
||||
|
||||
return new ListItemReader<>(sampleData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<logger name="org.apache.http" level="WARN"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
<logger name="org.zeroturnaround.exec" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -1,4 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS item
|
||||
(
|
||||
item_name varchar(55)
|
||||
);
|
||||
@@ -1,8 +0,0 @@
|
||||
1 2 3 4 5 six
|
||||
# This should be ignored
|
||||
7 8 9 1011twelve
|
||||
$ So should this
|
||||
1314151617eighteen
|
||||
1920212223twenty four
|
||||
1526272829thirty
|
||||
3132333435thirty six
|
||||
Binary file not shown.
|
@@ -1,12 +0,0 @@
|
||||
1@2@3@4@5@six
|
||||
# This should be ignored
|
||||
7@8@9@10@11@twelve
|
||||
$ So should this
|
||||
13@14@15@16@17@eighteen
|
||||
19@20@21@22@23@%twenty four%
|
||||
15@26@27@28@29@thirty
|
||||
31@32@33@34@35@thirty six
|
||||
37@38@39@40@41@forty two
|
||||
43@44@45@46@47@forty eight
|
||||
49@50@51@52@53@fifty four
|
||||
55@56@57@58@59@sixty
|
||||
|
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-parent</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-cloud-starter-task</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Task Starter</name>
|
||||
<description>Spring Boot starter for Spring Cloud Task</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-batch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-parent</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-cloud-task-batch</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Task Batch</name>
|
||||
<description>Module for use when combining Spring Cloud Task with Spring Batch
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-task-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-deployer-spi</artifactId>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<groupId>org.springframework</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-deployer-local</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-deployer-resource-support</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.batch</groupId>
|
||||
<artifactId>spring-batch-integration</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* Evaluates if the correct conditions have been met to create a TaskJobLauncher.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class JobLaunchCondition extends AllNestedConditions {
|
||||
|
||||
public JobLaunchCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = "spring.cloud.task.batch.fail-on-job-failure", havingValue = "true")
|
||||
static class FailOnJobFailureCondition {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.batch.job", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
static class SpringBatchJobCondition {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener;
|
||||
import org.springframework.cloud.task.configuration.TaskConfigurer;
|
||||
import org.springframework.cloud.task.configuration.TaskProperties;
|
||||
import org.springframework.cloud.task.listener.TaskLifecycleListener;
|
||||
import org.springframework.cloud.task.repository.TaskExplorer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Provides auto configuration for the {@link TaskBatchExecutionListener}.
|
||||
*
|
||||
* The spring.cloud.task.batch.listener.enable is deprecated,
|
||||
* spring.cloud.task.batch.listener.enabled should be used.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnBean({ Job.class, TaskLifecycleListener.class })
|
||||
@ConditionalOnProperty(name = { "spring.cloud.task.batch.listener.enable", "spring.cloud.task.batch.listener.enabled" },
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public class TaskBatchAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public static TaskBatchExecutionListenerBeanPostProcessor batchTaskExecutionListenerBeanPostProcessor() {
|
||||
return new TaskBatchExecutionListenerBeanPostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto configuration for {@link TaskBatchExecutionListener}.
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnMissingBean(name = "taskBatchExecutionListener")
|
||||
@EnableConfigurationProperties(TaskProperties.class)
|
||||
public static class TaskBatchExecutionListenerAutoconfiguration {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private TaskProperties taskProperties;
|
||||
|
||||
@Bean
|
||||
public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(TaskExplorer taskExplorer) {
|
||||
TaskConfigurer taskConfigurer = null;
|
||||
if (!this.context.getBeansOfType(TaskConfigurer.class).isEmpty()) {
|
||||
taskConfigurer = this.context.getBean(TaskConfigurer.class);
|
||||
}
|
||||
if (taskConfigurer != null && taskConfigurer.getTaskDataSource() != null) {
|
||||
return new TaskBatchExecutionListenerFactoryBean(taskConfigurer.getTaskDataSource(), taskExplorer,
|
||||
this.taskProperties.getTablePrefix());
|
||||
}
|
||||
else {
|
||||
return new TaskBatchExecutionListenerFactoryBean(null, taskExplorer,
|
||||
this.taskProperties.getTablePrefix());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.job.AbstractJob;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Injects a configured {@link TaskBatchExecutionListener} into any batch jobs (beans
|
||||
* assignable to {@link AbstractJob}) that are executed within the scope of a task. The
|
||||
* context this is used within is expected to have only one bean of type
|
||||
* {@link TaskBatchExecutionListener}.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class TaskBatchExecutionListenerBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private List<String> jobNames = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (this.jobNames.size() > 0 && !this.jobNames.contains(beanName)) {
|
||||
return bean;
|
||||
}
|
||||
int length = this.applicationContext.getBeanNamesForType(TaskBatchExecutionListener.class).length;
|
||||
|
||||
if (bean instanceof AbstractJob) {
|
||||
if (length != 1) {
|
||||
throw new IllegalStateException("The application context is required to "
|
||||
+ "have exactly 1 instance of the TaskBatchExecutionListener but has " + length);
|
||||
}
|
||||
((AbstractJob) bean)
|
||||
.registerJobExecutionListener(this.applicationContext.getBean(TaskBatchExecutionListener.class));
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
public void setJobNames(List<String> jobNames) {
|
||||
Assert.notNull(jobNames, "A list is required");
|
||||
|
||||
this.jobNames = jobNames;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener;
|
||||
import org.springframework.cloud.task.batch.listener.support.JdbcTaskBatchDao;
|
||||
import org.springframework.cloud.task.batch.listener.support.MapTaskBatchDao;
|
||||
import org.springframework.cloud.task.configuration.TaskProperties;
|
||||
import org.springframework.cloud.task.repository.TaskExplorer;
|
||||
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
|
||||
import org.springframework.cloud.task.repository.support.SimpleTaskExplorer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} for a {@link TaskBatchExecutionListener}. Provides a jdbc based
|
||||
* listener if there is a {@link DataSource} available. Otherwise, builds a listener that
|
||||
* uses the map based implementation.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class TaskBatchExecutionListenerFactoryBean implements FactoryBean<TaskBatchExecutionListener> {
|
||||
|
||||
private TaskBatchExecutionListener listener;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
private TaskExplorer taskExplorer;
|
||||
|
||||
private String tablePrefix = TaskProperties.DEFAULT_TABLE_PREFIX;
|
||||
|
||||
/**
|
||||
* Initializes the TaskBatchExecutionListenerFactoryBean and defaults the tablePrefix
|
||||
* to {@link TaskProperties#DEFAULT_TABLE_PREFIX}.
|
||||
* @param dataSource the dataSource to use for the TaskBatchExecutionListener.
|
||||
* @param taskExplorer the taskExplorer to use for the TaskBatchExecutionListener.
|
||||
*/
|
||||
public TaskBatchExecutionListenerFactoryBean(DataSource dataSource, TaskExplorer taskExplorer) {
|
||||
this.dataSource = dataSource;
|
||||
this.taskExplorer = taskExplorer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the TaskBatchExecutionListenerFactoryBean.
|
||||
* @param dataSource the dataSource to use for the TaskBatchExecutionListener.
|
||||
* @param taskExplorer the taskExplorer to use for the TaskBatchExecutionListener.
|
||||
* @param tablePrefix the prefix for the task tables accessed by the
|
||||
* TaskBatchExecutionListener.
|
||||
*/
|
||||
public TaskBatchExecutionListenerFactoryBean(DataSource dataSource, TaskExplorer taskExplorer, String tablePrefix) {
|
||||
this(dataSource, taskExplorer);
|
||||
Assert.hasText(tablePrefix, "tablePrefix must not be null nor empty.");
|
||||
this.tablePrefix = tablePrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBatchExecutionListener getObject() throws Exception {
|
||||
if (this.listener != null) {
|
||||
return this.listener;
|
||||
}
|
||||
if (this.dataSource == null) {
|
||||
this.listener = new TaskBatchExecutionListener(getMapTaskBatchDao());
|
||||
}
|
||||
else {
|
||||
this.listener = new TaskBatchExecutionListener(new JdbcTaskBatchDao(this.dataSource, this.tablePrefix));
|
||||
}
|
||||
|
||||
return this.listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return TaskBatchExecutionListener.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private MapTaskBatchDao getMapTaskBatchDao() throws Exception {
|
||||
Field taskExecutionDaoField = ReflectionUtils.findField(SimpleTaskExplorer.class, "taskExecutionDao");
|
||||
taskExecutionDaoField.setAccessible(true);
|
||||
|
||||
MapTaskExecutionDao taskExecutionDao;
|
||||
|
||||
if (AopUtils.isJdkDynamicProxy(this.taskExplorer)) {
|
||||
SimpleTaskExplorer dereferencedTaskRepository = (SimpleTaskExplorer) ((Advised) this.taskExplorer)
|
||||
.getTargetSource().getTarget();
|
||||
|
||||
taskExecutionDao = (MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField,
|
||||
dereferencedTaskRepository);
|
||||
}
|
||||
else {
|
||||
taskExecutionDao = (MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField, this.taskExplorer);
|
||||
}
|
||||
|
||||
return new MapTaskBatchDao(taskExecutionDao.getBatchJobAssociations());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Establish properties to be used for how Tasks work with Spring Batch.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Michael Minella
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.task.batch")
|
||||
public class TaskBatchProperties {
|
||||
|
||||
private static final long DEFAULT_POLL_INTERVAL = 5000L;
|
||||
|
||||
/**
|
||||
* Comma-separated list of job names to execute on startup (for instance,
|
||||
* `job1,job2`). By default, all Jobs found in the context are executed.
|
||||
* @deprecated use spring.batch.job.name instead of spring.cloud.task.batch.jobNames.
|
||||
*/
|
||||
private String jobNames = "";
|
||||
|
||||
/**
|
||||
* The order for the {@code ApplicationRunner} used to run batch jobs when
|
||||
* {@code spring.cloud.task.batch.fail-on-job-failure=true}. Defaults to 0 (same as
|
||||
* the
|
||||
* {@link org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner}).
|
||||
*/
|
||||
private int applicationRunnerOrder = 0;
|
||||
|
||||
/**
|
||||
* Fixed delay in milliseconds that Spring Cloud Task will wait when checking if
|
||||
* {@link org.springframework.batch.core.JobExecution}s have completed, when
|
||||
* spring.cloud.task.batch.failOnJobFailure is set to true. Defaults to 5000.
|
||||
*/
|
||||
private long failOnJobFailurePollInterval = DEFAULT_POLL_INTERVAL;
|
||||
|
||||
public String getJobNames() {
|
||||
return this.jobNames;
|
||||
}
|
||||
|
||||
public void setJobNames(String jobNames) {
|
||||
this.jobNames = jobNames;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public int getCommandLineRunnerOrder() {
|
||||
return this.applicationRunnerOrder;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setCommandLineRunnerOrder(int commandLineRunnerOrder) {
|
||||
this.applicationRunnerOrder = commandLineRunnerOrder;
|
||||
}
|
||||
|
||||
public int getApplicationRunnerOrder() {
|
||||
return this.applicationRunnerOrder;
|
||||
}
|
||||
|
||||
public void setApplicationRunnerOrder(int applicationRunnerOrder) {
|
||||
this.applicationRunnerOrder = applicationRunnerOrder;
|
||||
}
|
||||
|
||||
public long getFailOnJobFailurePollInterval() {
|
||||
return this.failOnJobFailurePollInterval;
|
||||
}
|
||||
|
||||
public void setFailOnJobFailurePollInterval(long failOnJobFailurePollInterval) {
|
||||
this.failOnJobFailurePollInterval = failOnJobFailurePollInterval;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchProperties;
|
||||
import org.springframework.cloud.task.batch.handler.TaskJobLauncherApplicationRunner;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Factory bean for creating an instance of {@link TaskJobLauncherApplicationRunner}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class TaskJobLauncherApplicationRunnerFactoryBean
|
||||
implements FactoryBean<TaskJobLauncherApplicationRunner>, ApplicationEventPublisherAware {
|
||||
|
||||
private final JobLauncher jobLauncher;
|
||||
|
||||
private final JobExplorer jobExplorer;
|
||||
|
||||
private final List<Job> jobs;
|
||||
|
||||
private String jobName;
|
||||
|
||||
private final JobRegistry jobRegistry;
|
||||
|
||||
private Integer order;
|
||||
|
||||
private final TaskBatchProperties taskBatchProperties;
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public TaskJobLauncherApplicationRunnerFactoryBean(JobLauncher jobLauncher, JobExplorer jobExplorer, List<Job> jobs,
|
||||
TaskBatchProperties taskBatchProperties, JobRegistry jobRegistry, JobRepository jobRepository,
|
||||
BatchProperties batchProperties) {
|
||||
Assert.notNull(taskBatchProperties, "taskBatchProperties must not be null");
|
||||
Assert.notNull(batchProperties, "batchProperties must not be null");
|
||||
Assert.notEmpty(jobs, "jobs must not be null nor empty");
|
||||
|
||||
this.jobLauncher = jobLauncher;
|
||||
this.jobExplorer = jobExplorer;
|
||||
this.jobs = jobs;
|
||||
this.jobName = taskBatchProperties.getJobNames();
|
||||
this.jobRegistry = jobRegistry;
|
||||
this.taskBatchProperties = taskBatchProperties;
|
||||
if (StringUtils.hasText(batchProperties.getJob().getName())) {
|
||||
this.jobName = batchProperties.getJob().getName();
|
||||
}
|
||||
else {
|
||||
this.jobName = taskBatchProperties.getJobNames();
|
||||
}
|
||||
this.order = taskBatchProperties.getApplicationRunnerOrder();
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskJobLauncherApplicationRunner getObject() {
|
||||
TaskJobLauncherApplicationRunner taskJobLauncherApplicationRunner = new TaskJobLauncherApplicationRunner(
|
||||
this.jobLauncher, this.jobExplorer, this.jobRepository, this.taskBatchProperties);
|
||||
taskJobLauncherApplicationRunner.setJobs(this.jobs);
|
||||
if (StringUtils.hasText(this.jobName)) {
|
||||
taskJobLauncherApplicationRunner.setJobName(this.jobName);
|
||||
}
|
||||
taskJobLauncherApplicationRunner.setJobRegistry(this.jobRegistry);
|
||||
|
||||
if (this.order != null) {
|
||||
taskJobLauncherApplicationRunner.setOrder(this.order);
|
||||
}
|
||||
if (this.applicationEventPublisher != null) {
|
||||
taskJobLauncherApplicationRunner.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
}
|
||||
return taskJobLauncherApplicationRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return TaskJobLauncherApplicationRunner.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.batch.BatchProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* Provides auto configuration for the
|
||||
* {@link org.springframework.cloud.task.batch.handler.TaskJobLauncherApplicationRunner}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@Conditional(JobLaunchCondition.class)
|
||||
@EnableConfigurationProperties(TaskBatchProperties.class)
|
||||
@AutoConfigureBefore(BatchAutoConfiguration.class)
|
||||
public class TaskJobLauncherAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private TaskBatchProperties properties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner")
|
||||
public TaskJobLauncherApplicationRunnerFactoryBean taskJobLauncherApplicationRunner(JobLauncher jobLauncher,
|
||||
JobExplorer jobExplorer, List<Job> jobs, JobRegistry jobRegistry, JobRepository jobRepository,
|
||||
BatchProperties batchProperties) {
|
||||
TaskJobLauncherApplicationRunnerFactoryBean taskJobLauncherApplicationRunnerFactoryBean;
|
||||
taskJobLauncherApplicationRunnerFactoryBean = new TaskJobLauncherApplicationRunnerFactoryBean(jobLauncher,
|
||||
jobExplorer, jobs, this.properties, jobRegistry, jobRepository, batchProperties);
|
||||
|
||||
return taskJobLauncherApplicationRunnerFactoryBean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobExecutionException;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.JobParametersIncrementer;
|
||||
import org.springframework.batch.core.JobParametersInvalidException;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.autoconfigure.batch.JobExecutionEvent;
|
||||
import org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner;
|
||||
import org.springframework.cloud.task.batch.configuration.TaskBatchProperties;
|
||||
import org.springframework.cloud.task.listener.TaskException;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ApplicationRunner} to {@link JobLauncher launch} Spring Batch jobs. Runs all
|
||||
* jobs in the surrounding context by default and throws an exception upon the first job
|
||||
* that returns an {@link BatchStatus} of FAILED if a {@link TaskExecutor} in the
|
||||
* {@link JobLauncher} is not specified. If a {@link TaskExecutor} is specified in the
|
||||
* {@link JobLauncher} then all Jobs are launched and an exception is thrown if one or
|
||||
* more of the jobs has an {@link BatchStatus} of FAILED. TaskJobLauncherApplicationRunner
|
||||
* can also be used to launch a specific job by providing a jobName. The
|
||||
* TaskJobLauncherApplicationRunner takes the place of the
|
||||
* {@link JobLauncherApplicationRunner} when it is in use.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class TaskJobLauncherApplicationRunner extends JobLauncherApplicationRunner {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TaskJobLauncherApplicationRunner.class);
|
||||
|
||||
private final JobLauncher taskJobLauncher;
|
||||
|
||||
private final JobExplorer taskJobExplorer;
|
||||
|
||||
private final JobRepository taskJobRepository;
|
||||
|
||||
private final List<JobExecution> jobExecutionList = new ArrayList<>();
|
||||
|
||||
private ApplicationEventPublisher taskApplicationEventPublisher;
|
||||
|
||||
private final TaskBatchProperties taskBatchProperties;
|
||||
|
||||
/**
|
||||
* Create a new {@link TaskJobLauncherApplicationRunner}.
|
||||
* @param jobLauncher to launch jobs
|
||||
* @param jobExplorer to check the job repository for previous executions
|
||||
* @param jobRepository to check if a job instance exists with the given parameters
|
||||
* when running a job
|
||||
* @param taskBatchProperties the properties used to configure the
|
||||
* taskBatchProperties.
|
||||
*/
|
||||
public TaskJobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer,
|
||||
JobRepository jobRepository, TaskBatchProperties taskBatchProperties) {
|
||||
super(jobLauncher, jobExplorer, jobRepository);
|
||||
this.taskJobLauncher = jobLauncher;
|
||||
this.taskJobExplorer = jobExplorer;
|
||||
this.taskJobRepository = jobRepository;
|
||||
this.taskBatchProperties = taskBatchProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
|
||||
super.setApplicationEventPublisher(publisher);
|
||||
this.taskApplicationEventPublisher = publisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws JobExecutionException {
|
||||
logger.info("Running default command line with: " + Arrays.asList(args));
|
||||
launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
|
||||
monitorJobExecutions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException,
|
||||
JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {
|
||||
String jobName = job.getName();
|
||||
JobParameters parameters = jobParameters;
|
||||
boolean jobInstanceExists = this.taskJobRepository.isJobInstanceExists(jobName, parameters);
|
||||
if (jobInstanceExists) {
|
||||
JobExecution lastJobExecution = this.taskJobRepository.getLastJobExecution(jobName, jobParameters);
|
||||
if (lastJobExecution != null && isStoppedOrFailed(lastJobExecution) && job.isRestartable()) {
|
||||
// Retry a failed or stopped execution with previous parameters
|
||||
JobParameters previousParameters = lastJobExecution.getJobParameters();
|
||||
/*
|
||||
* remove Non-identifying parameters from the previous execution's
|
||||
* parameters since there is no way to remove them programmatically. If
|
||||
* they are required (or need to be modified) on a restart, they need to
|
||||
* be (re)specified.
|
||||
*/
|
||||
JobParameters previousIdentifyingParameters = removeNonIdentifying(previousParameters);
|
||||
// merge additional parameters with previous ones (overriding those with
|
||||
// the same key)
|
||||
parameters = merge(previousIdentifyingParameters, jobParameters);
|
||||
}
|
||||
}
|
||||
else {
|
||||
JobParametersIncrementer incrementer = job.getJobParametersIncrementer();
|
||||
if (incrementer != null) {
|
||||
JobParameters nextParameters = new JobParametersBuilder(jobParameters, this.taskJobExplorer)
|
||||
.getNextJobParameters(job).toJobParameters();
|
||||
parameters = merge(nextParameters, jobParameters);
|
||||
}
|
||||
}
|
||||
JobExecution execution = this.taskJobLauncher.run(job, parameters);
|
||||
if (this.taskApplicationEventPublisher != null) {
|
||||
this.taskApplicationEventPublisher.publishEvent(new JobExecutionEvent(execution));
|
||||
}
|
||||
this.jobExecutionList.add(execution);
|
||||
if (execution.getStatus().equals(BatchStatus.FAILED)) {
|
||||
throwJobFailedException(Collections.singletonList(execution));
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorJobExecutions() {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
|
||||
template.iterate(context -> {
|
||||
|
||||
List<JobExecution> failedJobExecutions = new ArrayList<>();
|
||||
for (JobExecution jobExecution : this.jobExecutionList) {
|
||||
BatchStatus batchStatus = getCurrentBatchStatus(jobExecution);
|
||||
if (batchStatus.isRunning()) {
|
||||
Thread.sleep(this.taskBatchProperties.getFailOnJobFailurePollInterval());
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
if (batchStatus.equals(BatchStatus.FAILED)) {
|
||||
failedJobExecutions.add(jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedJobExecutions.size() > 0) {
|
||||
throwJobFailedException(failedJobExecutions);
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
});
|
||||
}
|
||||
|
||||
private BatchStatus getCurrentBatchStatus(JobExecution jobExecution) {
|
||||
if (jobExecution.getStatus().isRunning()) {
|
||||
return this.taskJobExplorer.getJobExecution(jobExecution.getId()).getStatus();
|
||||
}
|
||||
return jobExecution.getStatus();
|
||||
}
|
||||
|
||||
private void throwJobFailedException(List<JobExecution> failedJobExecutions) {
|
||||
StringBuilder message = new StringBuilder("The following Jobs have failed: \n");
|
||||
for (JobExecution failedJobExecution : failedJobExecutions) {
|
||||
message.append(String.format(
|
||||
"Job %s failed during " + "execution for job instance id %s with jobExecutionId of %s \n",
|
||||
failedJobExecution.getJobInstance().getJobName(), failedJobExecution.getJobId(),
|
||||
failedJobExecution.getId()));
|
||||
}
|
||||
|
||||
logger.error(message);
|
||||
|
||||
throw new TaskException(message.toString());
|
||||
|
||||
}
|
||||
|
||||
private JobParameters removeNonIdentifying(JobParameters parameters) {
|
||||
Map<String, JobParameter<?>> parameterMap = parameters.getParameters();
|
||||
HashMap<String, JobParameter<?>> copy = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, JobParameter<?>> parameter : parameterMap.entrySet()) {
|
||||
if (parameter.getValue().isIdentifying()) {
|
||||
copy.put(parameter.getKey(), parameter.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return new JobParameters(copy);
|
||||
}
|
||||
|
||||
private boolean isStoppedOrFailed(JobExecution execution) {
|
||||
BatchStatus status = execution.getStatus();
|
||||
return (status == BatchStatus.STOPPED || status == BatchStatus.FAILED);
|
||||
}
|
||||
|
||||
private JobParameters merge(JobParameters parameters, JobParameters additionals) {
|
||||
Map<String, JobParameter<?>> merged = new HashMap<>();
|
||||
merged.putAll(parameters.getParameters());
|
||||
merged.putAll(additionals.getParameters());
|
||||
return new JobParameters(merged);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.listener;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* Maintains the association between a {@link TaskExecution} and a {@link JobExecution}
|
||||
* executed within it.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public interface TaskBatchDao {
|
||||
|
||||
/**
|
||||
* Saves the relationship between a task execution and a job execution.
|
||||
* @param taskExecution task execution
|
||||
* @param jobExecution job execution
|
||||
*/
|
||||
void saveRelationship(TaskExecution taskExecution, JobExecution jobExecution);
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.batch.listener;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobExecutionListener;
|
||||
import org.springframework.cloud.task.listener.TaskExecutionListener;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Responsible for storing the relationship between a Spring Batch job and the Spring
|
||||
* Cloud task it was executed within.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class TaskBatchExecutionListener implements JobExecutionListener, Ordered, TaskExecutionListener {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TaskBatchExecutionListener.class);
|
||||
|
||||
private TaskExecution taskExecution;
|
||||
|
||||
private final TaskBatchDao taskBatchDao;
|
||||
|
||||
/**
|
||||
* @param taskBatchDao dao used to persist the relationship. Must not be null
|
||||
*/
|
||||
public TaskBatchExecutionListener(TaskBatchDao taskBatchDao) {
|
||||
Assert.notNull(taskBatchDao, "A TaskBatchDao is required");
|
||||
|
||||
this.taskBatchDao = taskBatchDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStartup(TaskExecution taskExecution) {
|
||||
this.taskExecution = taskExecution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeJob(JobExecution jobExecution) {
|
||||
if (this.taskExecution == null) {
|
||||
logger.warn("This job was executed outside the scope of a task but still used the task listener.");
|
||||
}
|
||||
else {
|
||||
logger.info(String.format("The job execution id %s was run within the task execution %s",
|
||||
jobExecution.getId(), this.taskExecution.getExecutionId()));
|
||||
this.taskBatchDao.saveRelationship(this.taskExecution, jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user