Initial Commit

Migrating the existing structure from the following location:
https://github.com/spring-cloud-stream-app-starters/stream-applications/tree/restructuring
This commit is contained in:
Soby Chacko
2020-05-04 17:50:58 -04:00
commit 517ccd5b40
383 changed files with 24851 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
apps/
/application.yml
/application.properties
asciidoctor.css
*~
.#*
*#
target/
build/
bin/
_site/
.classpath
.project
.settings
.springBeans
.DS_Store
*.sw*
*.iml
*.ipr
*.iws
.idea/
.factorypath
spring-xd-samples/*/xd
dump.rdb
coverage-error.log
.apt_generated
aws.credentials.properties

1
.mvn/jvm.config Normal file
View File

@@ -0,0 +1 @@
-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom

1
.mvn/maven.config Normal file
View File

@@ -0,0 +1 @@
-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring

117
.mvn/wrapper/MavenWrapperDownloader.java vendored Normal file
View 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.5";
/**
* 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 Normal file

Binary file not shown.

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar

1
README.adoc Normal file
View File

@@ -0,0 +1 @@
== Stream applications

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</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>
<!-- Gemstone repository is needed since gemfire libs
are not hosted in mvn central. Note that the libs
are also hosted and browsable at
https://repo.spring.io/gemstone-release-cache -->
<repository>
<id>gemstone-release</id>
<name>GemStone Maven Release Repository</name>
<url>http://dist.gemstone.com/maven/release</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>

View File

@@ -0,0 +1,44 @@
= 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/]

View File

@@ -0,0 +1,201 @@
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.

View File

@@ -0,0 +1,2 @@
#Core components shared by other projects in the app starters organization
This module consists of core dependencies and other common artifacts.

View File

@@ -0,0 +1,25 @@
<?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">
<parent>
<artifactId>stream-apps-parent</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-common</artifactId>
<packaging>pom</packaging>
<name>stream-apps-common</name>
<modules>
<module>stream-apps-file-common</module>
<module>stream-apps-ftp-common</module>
<module>stream-apps-test-support</module>
<module>stream-apps-postprocessor-common</module>
<module>stream-apps-micrometer-common</module>
<module>stream-apps-metadata-store-common</module>
<module>stream-apps-task-launch-request-common</module>
<module>stream-apps-security-common</module>
</modules>
</project>

View File

@@ -0,0 +1,21 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-file-common</artifactId>
<name>stream-apps-file-common</name>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-file</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2015-2016 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.file;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
/**
* @author David Turanski
* @author Artem Bilan
*/
@ConfigurationProperties("file.consumer")
@Validated
public class FileConsumerProperties {
/**
* The FileReadingMode to use for file reading sources.
* Values are 'ref' - The File object,
* 'lines' - a message per line, or
* 'contents' - the contents as bytes.
*/
private FileReadingMode mode = FileReadingMode.contents;
/**
* Set to true to emit start of file/end of file marker messages before/after the data.
* Only valid with FileReadingMode 'lines'.
*/
private Boolean withMarkers = null;
/**
* When 'fileMarkers == true', specify if they should be produced
* as FileSplitter.FileMarker objects or JSON.
*/
private boolean markersJson = true;
@NotNull
public FileReadingMode getMode() {
return this.mode;
}
public void setMode(FileReadingMode mode) {
this.mode = mode;
}
public Boolean getWithMarkers() {
return this.withMarkers;
}
public void setWithMarkers(Boolean withMarkers) {
this.withMarkers = withMarkers;
}
public boolean getMarkersJson() {
return this.markersJson;
}
public void setMarkersJson(boolean markersJson) {
this.markersJson = markersJson;
}
@AssertTrue(message = "withMarkers can only be supplied when FileReadingMode is 'lines'")
public boolean isWithMarkersValid() {
return this.withMarkers == null || FileReadingMode.lines == this.mode;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2015 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.stream.app.file;
/**
* Defines the supported modes of reading and processing files for the
* {@code File}, {@code FTP} and {@code SFTP} sources.
*
* @author Gunnar Hillert
* @author David Turanski
*/
public enum FileReadingMode {
ref,
lines,
contents;
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2015-2018 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.stream.app.file;
import java.util.Collections;
import org.springframework.integration.dsl.IntegrationFlowBuilder;
import org.springframework.integration.file.splitter.FileSplitter;
import org.springframework.integration.file.transformer.FileToByteArrayTransformer;
import org.springframework.integration.transformer.StreamTransformer;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.MimeTypeUtils;
/**
* @author Gary Russell
* @author Artem Bilan
* @author Christian Tzolov
*
*/
public class FileUtils {
/**
* Enhance an {@link IntegrationFlowBuilder} to add flow snippets, depending on
* {@link FileConsumerProperties}.
* @param flowBuilder the flow builder.
* @param fileConsumerProperties the properties.
* @return the updated flow builder.
*/
public static IntegrationFlowBuilder enhanceFlowForReadingMode(IntegrationFlowBuilder flowBuilder,
FileConsumerProperties fileConsumerProperties) {
switch (fileConsumerProperties.getMode()) {
case contents:
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE))
.transform(new FileToByteArrayTransformer());
break;
case lines:
Boolean withMarkers = fileConsumerProperties.getWithMarkers();
if (withMarkers == null) {
withMarkers = false;
}
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.TEXT_PLAIN_VALUE))
.split(new FileSplitter(true, withMarkers, fileConsumerProperties.getMarkersJson()));
break;
case ref:
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.APPLICATION_JSON_VALUE));
break;
default:
throw new IllegalArgumentException(fileConsumerProperties.getMode().name() +
" is not a supported file reading mode.");
}
return flowBuilder;
}
/**
* Enhance an {@link IntegrationFlowBuilder} to add flow snippets, depending on
* {@link FileConsumerProperties}; used for streaming sources.
* @param flowBuilder the flow builder.
* @param fileConsumerProperties the properties.
* @return the updated flow builder.
*/
public static IntegrationFlowBuilder enhanceStreamFlowForReadingMode(IntegrationFlowBuilder flowBuilder,
FileConsumerProperties fileConsumerProperties) {
switch (fileConsumerProperties.getMode()) {
case contents:
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE))
.transform(new StreamTransformer());
break;
case lines:
Boolean withMarkers = fileConsumerProperties.getWithMarkers();
if (withMarkers == null) {
withMarkers = false;
}
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.TEXT_PLAIN_VALUE))
.split(new FileSplitter(true, withMarkers, fileConsumerProperties.getMarkersJson()));
break;
case ref:
default:
throw new IllegalArgumentException(fileConsumerProperties.getMode().name() +
" is not a supported file reading mode when streaming.");
}
return flowBuilder;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2015-2017 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.stream.app.file.remote;
import org.hibernate.validator.constraints.NotBlank;
/**
* @deprecated - properties are flattened.
*
* @author Gary Russell
*
*/
@Deprecated
public abstract class AbstractRemoteFileProperties {
/**
* The remote FTP directory.
*/
private String remoteDir = "/";
/**
* The suffix to use while the transfer is in progress.
*/
private String tmpFileSuffix = ".tmp";
/**
* The remote file separator.
*/
private String remoteFileSeparator = "/";
@NotBlank
public String getRemoteDir() {
return remoteDir;
}
public final void setRemoteDir(String remoteDir) {
this.remoteDir = remoteDir;
}
@NotBlank
public String getTmpFileSuffix() {
return tmpFileSuffix;
}
public void setTmpFileSuffix(String tmpFileSuffix) {
this.tmpFileSuffix = tmpFileSuffix;
}
@NotBlank
public String getRemoteFileSeparator() {
return remoteFileSeparator;
}
public void setRemoteFileSeparator(String remoteFileSeparator) {
this.remoteFileSeparator = remoteFileSeparator;
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.file.remote;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.expression.Expression;
import org.springframework.integration.file.support.FileExistsMode;
/**
* @deprecated - properties are flattened.
*
* @author Gary Russell
*
*/
@Deprecated
public abstract class AbstractRemoteFileSinkProperties extends AbstractRemoteFileProperties {
/**
* A temporary directory where the file will be written if {@link #isUseTemporaryFilename()}
* is true.
*/
private String temporaryRemoteDir = "/";
/**
* Whether or not to create the remote directory.
*/
private boolean autoCreateDir = true;
/**
* Action to take if the remote file already exists.
*/
private FileExistsMode mode = FileExistsMode.REPLACE;
/**
* Whether or not to write to a temporary file and rename.
*/
private boolean useTemporaryFilename = true;
/**
* A SpEL expression to generate the remote file name.
*/
private Expression filenameExpression;
@NotBlank
public String getTemporaryRemoteDir() {
return this.temporaryRemoteDir;
}
public void setTemporaryRemoteDir(String temporaryRemoteDir) {
this.temporaryRemoteDir = temporaryRemoteDir;
}
public boolean isAutoCreateDir() {
return this.autoCreateDir;
}
public void setAutoCreateDir(boolean autoCreateDir) {
this.autoCreateDir = autoCreateDir;
}
@NotNull
public FileExistsMode getMode() {
return this.mode;
}
public void setMode(FileExistsMode mode) {
this.mode = mode;
}
public boolean isUseTemporaryFilename() {
return this.useTemporaryFilename;
}
public void setUseTemporaryFilename(boolean useTemporaryFilename) {
this.useTemporaryFilename = useTemporaryFilename;
}
public Expression getFilenameExpression() {
return this.filenameExpression;
}
public void setFilenameExpression(Expression filenameExpression) {
this.filenameExpression = filenameExpression;
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2015-2017 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.stream.app.file.remote;
import java.io.File;
import java.util.regex.Pattern;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
/**
* Common properties for remote file sources (e.g. (S)FTP).
*
* @deprecated - properties are flattened.
*
* @author David Turanski
* @author Gary Russell
*
*/
@Deprecated
public abstract class AbstractRemoteFileSourceProperties extends AbstractRemoteFileProperties {
/**
* Set to true to delete remote files after successful transfer.
*/
private boolean deleteRemoteFiles = false;
/**
* The local directory to use for file transfers.
*/
private File localDir = new File(System.getProperty("java.io.tmpdir") + "/xd/ftp");
/**
* Set to true to create the local directory if it does not exist.
*/
private boolean autoCreateLocalDir = true;
/**
* A filter pattern to match the names of files to transfer.
*/
private String filenamePattern;
/**
* A filter regex pattern to match the names of files to transfer.
*/
private Pattern filenameRegex;
/**
* Set to true to preserve the original timestamp.
*/
private boolean preserveTimestamp = true;
public boolean isAutoCreateLocalDir() {
return autoCreateLocalDir;
}
public void setAutoCreateLocalDir(boolean autoCreateLocalDir) {
this.autoCreateLocalDir = autoCreateLocalDir;
}
public boolean isDeleteRemoteFiles() {
return deleteRemoteFiles;
}
public void setDeleteRemoteFiles(boolean deleteRemoteFiles) {
this.deleteRemoteFiles = deleteRemoteFiles;
}
@NotNull
public File getLocalDir() {
return localDir;
}
public final void setLocalDir(File localDir) {
this.localDir = localDir;
}
public String getFilenamePattern() {
return filenamePattern;
}
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
public Pattern getFilenameRegex() {
return filenameRegex;
}
public void setFilenameRegex(Pattern filenameRegex) {
this.filenameRegex = filenameRegex;
}
public boolean isPreserveTimestamp() {
return preserveTimestamp;
}
public void setPreserveTimestamp(boolean preserveTimestamp) {
this.preserveTimestamp = preserveTimestamp;
}
@AssertTrue(message = "filenamePattern and filenameRegex are mutually exclusive")
public boolean isExclusivePatterns() {
return !(this.filenamePattern != null && this.filenameRegex != null);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.file.remote;
import org.hibernate.validator.constraints.NotBlank;
/**
* Common properties for remote servers (e.g. (S)FTP).
*
* @deprecated - properties are flattened.
*
* @author David Turanski
* @author Gary Russell
*
*/
@Deprecated
public abstract class AbstractRemoteServerProperties {
/**
* The host name of the server.
*/
private String host = "localhost";
/**
* The username to use to connect to the server.
*/
private String username;
/**
* The password to use to connect to the server.
*/
private String password;
/**
* Cache sessions
*/
private Boolean cacheSessions;
@NotBlank
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
@NotBlank
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getCacheSessions() {
return this.cacheSessions;
}
public void setCacheSessions(Boolean cacheSessions) {
this.cacheSessions = cacheSessions;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2018 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.stream.app.file.remote;
import java.nio.file.Paths;
import org.springframework.integration.file.FileHeaders;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* @author David Turanski
**/
public abstract class FilePathUtils {
/**
* Returns a remote file path for a message with a file name as payload and {@link FileHeaders#REMOTE_DIRECTORY}
* included as a message header.
*
* @param message the message containing the header.
* @return the file path.
*/
@Nullable
public static String getRemoteFilePath(Message message) {
if (message.getHeaders().containsKey(FileHeaders.REMOTE_DIRECTORY)) {
String filename = (String) message.getPayload();
String remoteDirectory = (String) message.getHeaders().get(FileHeaders.REMOTE_DIRECTORY);
return getPath(remoteDirectory, filename);
}
return null;
}
public static String getLocalFilePath(String localDirectory, String filename) {
if (localDirectory != null) {
return getPath(localDirectory, filename);
}
return filename;
}
private static String getPath(String dirName, String fileName) {
return Paths.get(dirName, fileName).toString();
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2017 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.stream.app.file.remote;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.transaction.IntegrationResourceHolder;
import org.springframework.integration.transaction.TransactionSynchronizationProcessor;
/**
* A {@link TransactionSynchronizationProcessor} that deletes a remote file on
* success.
*
* @author Gary Russell
*
*/
public class RemoteFileDeletingTransactionSynchronizationProcessor implements TransactionSynchronizationProcessor {
private final RemoteFileTemplate<?> template;
private final String remoteFileSeparator;
/**
* Construct an instance with the provided template and separator.
* @param template the template.
* @param remoteFileSeparator the separator.
*/
public RemoteFileDeletingTransactionSynchronizationProcessor(RemoteFileTemplate<?> template,
String remoteFileSeparator) {
this.template = template;
this.remoteFileSeparator = remoteFileSeparator;
}
@Override
public void processBeforeCommit(IntegrationResourceHolder holder) {
}
@Override
public void processAfterRollback(IntegrationResourceHolder holder) {
}
@Override
public void processAfterCommit(IntegrationResourceHolder holder) {
String remoteDir = (String) holder.getMessage().getHeaders().get(FileHeaders.REMOTE_DIRECTORY);
String remoteFile = (String) holder.getMessage().getHeaders().get(FileHeaders.REMOTE_FILE);
this.template.remove(remoteDir + this.remoteFileSeparator + remoteFile);
}
}

View File

@@ -0,0 +1,26 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-ftp-common</artifactId>
<name>stream-apps-ftp-common</name>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-ftp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>stream-apps-file-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2015-2016 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.ftp;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.ftp.session.DefaultFtpSessionFactory;
/**
* FTP Session factory configuration.
*
* @author David Turanski
* @author Gary Russell
*/
@Configuration
@EnableConfigurationProperties(FtpSessionFactoryProperties.class)
public class FtpSessionFactoryConfiguration {
@Bean
@ConditionalOnMissingBean
public SessionFactory<FTPFile> ftpSessionFactory(FtpSessionFactoryProperties properties) {
DefaultFtpSessionFactory ftpSessionFactory = new DefaultFtpSessionFactory();
ftpSessionFactory.setHost(properties.getHost());
ftpSessionFactory.setPort(properties.getPort());
ftpSessionFactory.setUsername(properties.getUsername());
ftpSessionFactory.setPassword(properties.getPassword());
ftpSessionFactory.setClientMode(properties.getClientMode().getMode());
if (properties.getCacheSessions() != null) {
CachingSessionFactory<FTPFile> csf = new CachingSessionFactory<>(ftpSessionFactory);
return csf;
}
else {
return ftpSessionFactory;
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2015-2016 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.ftp;
import javax.validation.constraints.NotNull;
import org.apache.commons.net.ftp.FTPClient;
import org.hibernate.validator.constraints.Range;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.stream.app.file.remote.AbstractRemoteServerProperties;
import org.springframework.validation.annotation.Validated;
/**
* FTP {@code SessionFactory} properties.
*
* @author David Turanski
* @author Gary Russell
*/
@ConfigurationProperties("ftp.factory")
@Validated
public class FtpSessionFactoryProperties extends AbstractRemoteServerProperties {
/**
* The port of the server.
*/
private int port = 21;
/**
* The client mode to use for the FTP session.
*/
private ClientMode clientMode = ClientMode.PASSIVE;
@Range(min = 0, max = 65535)
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
@NotNull
public ClientMode getClientMode() {
return this.clientMode;
}
public void setClientMode(ClientMode clientMode) {
this.clientMode = clientMode;
}
public static enum ClientMode {
ACTIVE(FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE),
PASSIVE(FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE);
private final int mode;
private ClientMode(int mode) {
this.mode = mode;
}
public int getMode() {
return mode;
}
}
}

View File

@@ -0,0 +1,181 @@
=== `MetadataStore` Common Module
This artifact contains a Spring Boot auto-configuration for the `MetadataStore`which can be used in various Spring Integration scenarios, like file polling, idempotent receiver, offset management etc.
See Spring Integration "`https://docs.spring.io/spring-integration/docs/5.0.6.RELEASE/reference/html/system-management-chapter.html#metadata-store[Reference Manual]`" for more information.
In addition to the standard Spring Boot configuration properties this module exposes a `MetadataStoreProperties` with the `metadata.store` prefix.
To auto-configure particular `MetadataStore` you just need to bring respective dependencies into the target app starter:
==== Redis
The `RedisMetadataStore` requires regular Spring Boot auto-configuration for https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-redis[Spring Data Redis] and minimal set of dependencies is like this:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
----
Additional configuration property for `RedisMetadataStore` is:
$$metadata.store.redis.key$$:: $$Redis key for metadata.$$ *($$String$$, default: `$$MetaData$$`)*
==== MongoDb
The `MongoDbMetadataStore` requires regular Spring Boot auto-configuration for https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-mongodb[Spring Data MongoDB] and minimal set of dependencies is like this:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
----
Additional configuration property for `MongoDbMetadataStore` is:
$$metadata.store.mongo-db.collection$$:: $$MongoDB collection name for metadata.$$ *($$String$$, default: `$$metadataStore$$`)*
==== Pivotal Gemfire / Apache Geode
The `GemfireMetadataStore` requires these dependencies for auto-configuration:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-gemfire</artifactId>
</dependency>
----
or when your environment is based on the Open Source https://geode.apache.org/[Apache Geode]:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-gemfire</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-geode</artifactId>
</dependency>
----
You also can consider to use https://github.com/spring-projects/spring-boot-data-geode[Spring Boot Data Geode] instead for automatic dependency management and proper Spring Boot auto-configuration for Pivotal Gemfire/Apache Geode.
Additional configuration property for `GemfireMetadataStore` is:
$$metadata.store.gemfire.region$$:: $$Gemfire region name for metadata.$$ *($$String$$, default: `$$MetaData$$`)*
In addition, for the `GemfireMetadataStore`, a `MetadataStoreListener` bean can be configured in the application context to react to the `MetadataStore` events.
A default auto-configured `ClientRegionFactoryBean`, based on the auto-configured `GemFireCache`, bean can be overridden in the target application.
==== Hazelcast
The `HazelcastMetadataStore` requires regular Spring Boot auto-configuration for https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-caching-provider-hazelcast[Hazelcast] and minimal set of dependencies is like this:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-hazelcast</artifactId>
</dependency>
----
There are no additional configuration properties for the `HazelcastMetadataStore`, however a `MetadataStoreListener` bean can be configured in the application context to react to the `MetadataStore` events.
==== Zookeeper
The `ZookeeperMetadataStore` requires this dependency for auto-configuration:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-zookeeper</artifactId>
</dependency>
----
The configuration properties for `ZookeeperMetadataStore` are:
$$metadata.store.zookeeper.connect-string$$:: $$Zookeeper connect string in form HOST:PORT.$$ *($$String$$, default: `$$127.0.0.1:2181$$`)*
$$metadata.store.zookeeper.retry-interval$$:: $$Retry interval for Zookeeper operations in milliseconds.$$ *($$int$$, default: `$$1000$$`)*
$$metadata.store.zookeeper.encoding$$:: $$Encoding to use when storing data in Zookeeper.$$ *($$Charset$$, default: `$$UTF-8$$`)*
$$metadata.store.zookeeper.root$$:: $$Root node - store entries are children of this node.$$ *($$String$$, default: `$$/SpringIntegration-MetadataStore$$`)*
In addition, for the `ZookeeperMetadataStore`, a `MetadataStoreListener` bean can be configured in the application context to react to the `MetadataStore` events.
Also a `CuratorFramework` bean can be provided to override a default auto-configured one.
==== AWS DymanoDb
The `DynamoDbMetadataStore` requires regular Spring Cloud AWS auto-configuration for https://cloud.spring.io/spring-cloud-static/spring-cloud-aws/2.0.0.RELEASE/single/spring-cloud-aws.html#_spring_boot_auto_configuration[Spring Boot] and minimal set of dependencies is like this:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-aws</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
</dependency>
----
Additional configuration properties for `DynamoDbMetadataStore` are:
$$metadata.store.dynamo-db.table:: $$Table name for metadata.$$ *($$String$$, default: `$$SpringIntegrationMetadataStore$$`)*
$$metadata.store.dynamo-db.read-capacity:: $$Read capacity on the table.$$ *($$long$$, default: `$$1$$`)*
$$metadata.store.dynamo-db.write-capacity:: $$Write capacity on the table.$$ *($$long$$, default: `$$1$$`)*
$$metadata.store.dynamo-db.create-delay:: $$Delay between create table retries.$$ *($$int$$, default: `$$1$$`)*
$$metadata.store.dynamo-db.create-retries:: $$Retry number for create table request.$$ *($$int$$, default: `$$25$$`)*
$$metadata.store.dynamo-db.time-to-live:: $$TTL for table entries.$$ *($$Integer$$, default: `$$<none>$$`)*
A default, auto-configured `AmazonDynamoDBAsync` bean can be overridden in the target application.
==== JDBC
The `JdbcMetadataStore` requires regular Spring Boot auto-configuration for https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-sql[JDBC DataSource] and minimal set of dependencies is like this:
[source,xml]
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
----
Plus vendor-specific JDBC driver artifact(s).
Additional configuration properties for `JdbcMetadataStore` are:
$$metadata.store.jdbc.table-prefix:: $$Prefix for the custom table name.$$ *($$String$$, default: `$$INT_$$`)*
$$metadata.store.jdbc.region:: $$Unique grouping identifier for messages persisted with this store.$$ *($$String$$, default: `$$DEFAULT$$`)*
When no any of those technologies dependencies are preset, an in-memory `SimpleMetadataStore` is auto-configured.
The target application can also provide its own `MetadataStore` bean to override any auto-configuration hooks.

View File

@@ -0,0 +1,152 @@
<?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>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>stream-apps-metadata-store-common</artifactId>
<name>stream-apps-metadata-store-common</name>
<properties>
<aws-java-sdk.version>1.11.439</aws-java-sdk.version>
<spring-integration-aws.version>2.0.0.RELEASE</spring-integration-aws.version>
<spring-integration-hazelcast.version>1.0.0.RELEASE</spring-integration-hazelcast.version>
<curator.version>4.0.1</curator.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
<!--Redis-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
<!--MongoDB-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mongodb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<!--Gemfire-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-gemfire</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--TODO-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-geode</artifactId>
<optional>true</optional>
</dependency>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>geode-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>-->
<!--JDBC-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<!--Zookeeper-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-zookeeper</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>${curator.version}</version>
<scope>test</scope>
</dependency>
<!--Hazelcast-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-hazelcast</artifactId>
<version>${spring-integration-hazelcast.version}</version>
<optional>true</optional>
</dependency>
<!--DynamoDB-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-aws</artifactId>
<version>${spring-integration-aws.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
<version>${aws-java-sdk.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2018 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.stream.app.metadata;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.client.ClientCache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
/**
* TODO
*
* This is the copy of {@code org.springframework.boot.data.geode.autoconfigure.ClientCacheAutoConfiguration}
* until {@code geode-spring-boot-starter} is released.
*
* @author John Blum
*/
@Configuration
@ConditionalOnClass({ ClientCacheFactoryBean.class, ClientCache.class })
@ConditionalOnMissingBean(GemFireCache.class)
@ClientCacheApplication
@EnablePdx
public class ClientCacheAutoConfiguration {
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2018 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.stream.app.metadata;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryForever;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.aws.core.region.RegionProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.integration.aws.metadata.DynamoDbMetadataStore;
import org.springframework.integration.gemfire.metadata.GemfireMetadataStore;
import org.springframework.integration.hazelcast.metadata.HazelcastMetadataStore;
import org.springframework.integration.jdbc.metadata.JdbcMetadataStore;
import org.springframework.integration.metadata.ConcurrentMetadataStore;
import org.springframework.integration.metadata.MetadataStoreListener;
import org.springframework.integration.metadata.SimpleMetadataStore;
import org.springframework.integration.mongodb.metadata.MongoDbMetadataStore;
import org.springframework.integration.redis.metadata.RedisMetadataStore;
import org.springframework.integration.zookeeper.metadata.ZookeeperMetadataStore;
import org.springframework.jdbc.core.JdbcTemplate;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsync;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsyncClientBuilder;
import com.hazelcast.core.HazelcastInstance;
/**
* @author Artem Bilan
*
* @since 2.0.2
*/
@Configuration
@ConditionalOnClass(ConcurrentMetadataStore.class)
@EnableConfigurationProperties(MetadataStoreProperties.class)
public class MetadataStoreAutoConfiguration {
@ConditionalOnClass(RedisMetadataStore.class)
@ConditionalOnBean(RedisTemplate.class)
static class Redis {
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore redisMetadataStore(RedisTemplate<String, ?> redisTemplate,
MetadataStoreProperties metadataStoreProperties) {
return new RedisMetadataStore(redisTemplate, metadataStoreProperties.getRedis().getKey());
}
}
@ConditionalOnClass(MongoDbMetadataStore.class)
@ConditionalOnBean(MongoTemplate.class)
static class Mongo {
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore mongoDbMetadataStore(MongoTemplate mongoTemplate,
MetadataStoreProperties metadataStoreProperties) {
return new MongoDbMetadataStore(mongoTemplate, metadataStoreProperties.getMongoDb().getCollection());
}
}
@ConditionalOnClass(GemfireMetadataStore.class)
@Import(ClientCacheAutoConfiguration.class)
static class Gemfire {
@Bean
@ConditionalOnMissingBean
public ClientRegionFactoryBean<?, ?> gemfireRegion(GemFireCache cache,
MetadataStoreProperties metadataStoreProperties) {
ClientRegionFactoryBean<?, ?> clientRegionFactoryBean = new ClientRegionFactoryBean<>();
clientRegionFactoryBean.setCache(cache);
clientRegionFactoryBean.setName(metadataStoreProperties.getGemfire().getRegion());
return clientRegionFactoryBean;
}
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore gemfireMetadataStore(Region<?, ?> region,
ObjectProvider<MetadataStoreListener> metadataStoreListenerObjectProvider) {
@SuppressWarnings("unchecked")
GemfireMetadataStore gemfireMetadataStore = new GemfireMetadataStore((Region<String, String>) region);
metadataStoreListenerObjectProvider.ifAvailable(gemfireMetadataStore::addListener);
return gemfireMetadataStore;
}
}
@ConditionalOnClass(HazelcastMetadataStore.class)
static class Hazelcast {
@Bean
@ConditionalOnMissingBean
public HazelcastInstance hazelcastInstance() {
return com.hazelcast.core.Hazelcast.newHazelcastInstance();
}
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore hazelcastMetadataStore(HazelcastInstance hazelcastInstance,
ObjectProvider<MetadataStoreListener> metadataStoreListenerObjectProvider) {
HazelcastMetadataStore hazelcastMetadataStore = new HazelcastMetadataStore(hazelcastInstance);
metadataStoreListenerObjectProvider.ifAvailable(hazelcastMetadataStore::addListener);
return hazelcastMetadataStore;
}
}
@ConditionalOnClass({ ZookeeperMetadataStore.class, CuratorFramework.class })
static class Zookeeper {
@Bean(initMethod = "start")
@ConditionalOnMissingBean
public CuratorFramework curatorFramework(MetadataStoreProperties metadataStoreProperties) {
MetadataStoreProperties.Zookeeper zookeeperProperties = metadataStoreProperties.getZookeeper();
return CuratorFrameworkFactory.newClient(zookeeperProperties.getConnectString(),
new RetryForever(zookeeperProperties.getRetryInterval()));
}
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore zookeeperMetadataStore(CuratorFramework curatorFramework,
MetadataStoreProperties metadataStoreProperties,
ObjectProvider<MetadataStoreListener> metadataStoreListenerObjectProvider) {
MetadataStoreProperties.Zookeeper zookeeperProperties = metadataStoreProperties.getZookeeper();
ZookeeperMetadataStore zookeeperMetadataStore = new ZookeeperMetadataStore(curatorFramework);
zookeeperMetadataStore.setEncoding(zookeeperProperties.getEncoding().name());
zookeeperMetadataStore.setRoot(zookeeperProperties.getRoot());
metadataStoreListenerObjectProvider.ifAvailable(zookeeperMetadataStore::addListener);
return zookeeperMetadataStore;
}
}
@ConditionalOnClass(DynamoDbMetadataStore.class)
@ConditionalOnBean({ AWSCredentialsProvider.class, RegionProvider.class })
static class DynamoDb {
@Bean
@ConditionalOnMissingBean
public AmazonDynamoDBAsync dynamoDB(AWSCredentialsProvider awsCredentialsProvider,
RegionProvider regionProvider) {
return AmazonDynamoDBAsyncClientBuilder.standard()
.withCredentials(awsCredentialsProvider)
.withRegion(
regionProvider.getRegion()
.getName())
.build();
}
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore dynamoDbMetadataStore(AmazonDynamoDBAsync dynamoDB,
MetadataStoreProperties metadataStoreProperties) {
MetadataStoreProperties.DynamoDb dynamoDbProperties = metadataStoreProperties.getDynamoDb();
DynamoDbMetadataStore dynamoDbMetadataStore =
new DynamoDbMetadataStore(dynamoDB, dynamoDbProperties.getTable());
dynamoDbMetadataStore.setReadCapacity(dynamoDbProperties.getReadCapacity());
dynamoDbMetadataStore.setWriteCapacity(dynamoDbProperties.getWriteCapacity());
dynamoDbMetadataStore.setCreateTableDelay(dynamoDbProperties.getCreateDelay());
dynamoDbMetadataStore.setCreateTableRetries(dynamoDbProperties.getCreateRetries());
if (dynamoDbProperties.getTimeToLive() != null) {
dynamoDbMetadataStore.setTimeToLive(dynamoDbProperties.getTimeToLive());
}
return dynamoDbMetadataStore;
}
}
@ConditionalOnClass(JdbcMetadataStore.class)
@ConditionalOnBean(JdbcTemplate.class)
static class Jdbc {
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore jdbcMetadataStore(JdbcTemplate jdbcTemplate,
MetadataStoreProperties metadataStoreProperties) {
MetadataStoreProperties.Jdbc jdbcProperties = metadataStoreProperties.getJdbc();
JdbcMetadataStore jdbcMetadataStore = new JdbcMetadataStore(jdbcTemplate);
jdbcMetadataStore.setTablePrefix(jdbcProperties.getTablePrefix());
jdbcMetadataStore.setRegion(jdbcProperties.getRegion());
return jdbcMetadataStore;
}
}
@Bean
@ConditionalOnMissingBean
public ConcurrentMetadataStore simpleMetadataStore() {
return new SimpleMetadataStore();
}
}

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2018 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.stream.app.metadata;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.integration.aws.metadata.DynamoDbMetadataStore;
import org.springframework.integration.gemfire.metadata.GemfireMetadataStore;
import org.springframework.integration.jdbc.metadata.JdbcMetadataStore;
import org.springframework.integration.redis.metadata.RedisMetadataStore;
/**
* @author Artem Bilan
*
* @since 2.0.2
*/
@ConfigurationProperties("metadata.store")
public class MetadataStoreProperties {
private final Mongo mongoDb = new Mongo();
private final Gemfire gemfire = new Gemfire();
private final Redis redis = new Redis();
private final DynamoDb dynamoDb = new DynamoDb();
private final Jdbc jdbc = new Jdbc();
private final Zookeeper zookeeper = new Zookeeper();
public Mongo getMongoDb() {
return this.mongoDb;
}
public Gemfire getGemfire() {
return this.gemfire;
}
public Redis getRedis() {
return this.redis;
}
public DynamoDb getDynamoDb() {
return this.dynamoDb;
}
public Jdbc getJdbc() {
return this.jdbc;
}
public Zookeeper getZookeeper() {
return this.zookeeper;
}
public static class Mongo {
/**
* MongoDB collection name for metadata.
*/
private String collection = "metadataStore";
public String getCollection() {
return this.collection;
}
public void setCollection(String collection) {
this.collection = collection;
}
}
public static class Gemfire {
/**
* Gemfire region name for metadata.
*/
private String region = GemfireMetadataStore.KEY;
public String getRegion() {
return this.region;
}
public void setRegion(String region) {
this.region = region;
}
}
public static class Redis {
/**
* Redis key for metadata.
*/
private String key = RedisMetadataStore.KEY;
public String getKey() {
return this.key;
}
public void setKey(String key) {
this.key = key;
}
}
public static class DynamoDb {
/**
* Table name for metadata.
*/
private String table = DynamoDbMetadataStore.DEFAULT_TABLE_NAME;
/**
* Read capacity on the table.
*/
private long readCapacity = 1L;
/**
* Write capacity on the table.
*/
private long writeCapacity = 1L;
/**
* Delay between create table retries.
*/
private int createDelay = 1;
/**
* Retry number for create table request.
*/
private int createRetries = 25;
/**
* TTL for table entries.
*/
private Integer timeToLive;
public String getTable() {
return this.table;
}
public void setTable(String table) {
this.table = table;
}
public long getReadCapacity() {
return this.readCapacity;
}
public void setReadCapacity(long readCapacity) {
this.readCapacity = readCapacity;
}
public long getWriteCapacity() {
return this.writeCapacity;
}
public void setWriteCapacity(long writeCapacity) {
this.writeCapacity = writeCapacity;
}
public int getCreateDelay() {
return this.createDelay;
}
public void setCreateDelay(int createDelay) {
this.createDelay = createDelay;
}
public int getCreateRetries() {
return this.createRetries;
}
public void setCreateRetries(int createRetries) {
this.createRetries = createRetries;
}
public Integer getTimeToLive() {
return this.timeToLive;
}
public void setTimeToLive(Integer timeToLive) {
this.timeToLive = timeToLive;
}
}
public static class Jdbc {
/**
* Prefix for the custom table name.
*/
private String tablePrefix = JdbcMetadataStore.DEFAULT_TABLE_PREFIX;
/**
* Unique grouping identifier for messages persisted with this store.
*/
private String region = "DEFAULT";
public String getTablePrefix() {
return this.tablePrefix;
}
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
public String getRegion() {
return this.region;
}
public void setRegion(String region) {
this.region = region;
}
}
public static class Zookeeper {
/**
* Zookeeper connect string in form HOST:PORT.
*/
private String connectString = "127.0.0.1:2181";
/**
* Retry interval for Zookeeper operations in milliseconds.
*/
private int retryInterval = 1000;
/**
* Encoding to use when storing data in Zookeeper.
*/
private Charset encoding = StandardCharsets.UTF_8;
/**
* Root node - store entries are children of this node.
*/
private String root = "/SpringIntegration-MetadataStore";
public String getConnectString() {
return connectString;
}
public void setConnectString(String connectString) {
this.connectString = connectString;
}
public int getRetryInterval() {
return this.retryInterval;
}
public void setRetryInterval(int retryInterval) {
this.retryInterval = retryInterval;
}
public Charset getEncoding() {
return this.encoding;
}
public void setEncoding(Charset encoding) {
this.encoding = encoding;
}
public String getRoot() {
return this.root;
}
public void setRoot(String root) {
this.root = root;
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.stream.app.metadata.MetadataStoreAutoConfiguration

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2018 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.stream.app.metadata;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.mock;
import java.beans.Introspector;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.apache.curator.test.TestingServer;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.aws.core.region.RegionProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.aws.metadata.DynamoDbMetadataStore;
import org.springframework.integration.gemfire.metadata.GemfireMetadataStore;
import org.springframework.integration.hazelcast.metadata.HazelcastMetadataStore;
import org.springframework.integration.jdbc.metadata.JdbcMetadataStore;
import org.springframework.integration.metadata.ConcurrentMetadataStore;
import org.springframework.integration.metadata.MetadataStore;
import org.springframework.integration.metadata.SimpleMetadataStore;
import org.springframework.integration.mongodb.metadata.MongoDbMetadataStore;
import org.springframework.integration.redis.metadata.RedisMetadataStore;
import org.springframework.integration.zookeeper.metadata.ZookeeperMetadataStore;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsync;
import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest;
import com.amazonaws.services.dynamodbv2.model.DescribeTableResult;
/**
* @author Artem Bilan
*
* @since 2.0.2
*/
@RunWith(Parameterized.class)
@Ignore
public class MetadataStoreAutoConfigurationTests {
private final static List<Class<? extends ConcurrentMetadataStore>> METADATA_STORE_CLASSES =
Arrays.asList(
RedisMetadataStore.class,
MongoDbMetadataStore.class,
GemfireMetadataStore.class,
JdbcMetadataStore.class,
ZookeeperMetadataStore.class,
HazelcastMetadataStore.class,
DynamoDbMetadataStore.class,
SimpleMetadataStore.class
);
private static FilteredClassLoader filteredClassLoaderBut(Class<? extends ConcurrentMetadataStore> classToInclude) {
return new FilteredClassLoader(
METADATA_STORE_CLASSES.stream()
.filter(Predicate.isEqual(classToInclude).negate())
.toArray(Class<?>[]::new));
}
private final ApplicationContextRunner contextRunner;
private final Class<? extends ConcurrentMetadataStore> classToInclude;
public MetadataStoreAutoConfigurationTests(Class<? extends ConcurrentMetadataStore> classToInclude) {
this.classToInclude = classToInclude;
this.contextRunner =
new ApplicationContextRunner()
.withUserConfiguration(TestConfiguration.class)
.withClassLoader(filteredClassLoaderBut(classToInclude));
}
@Parameterized.Parameters
public static Iterable<?> parameters() {
return METADATA_STORE_CLASSES;
}
@Test
public void testMetadataStore() {
this.contextRunner
.run(context -> {
assertThat(context.getBeansOfType(MetadataStore.class)).hasSize(1);
assertThat(context.getBeanNamesForType(this.classToInclude))
.containsOnlyOnce(Introspector.decapitalize(this.classToInclude.getSimpleName()));
});
}
@Configuration
@EnableAutoConfiguration
public static class TestConfiguration {
@Bean(destroyMethod = "stop")
@ConditionalOnClass(ZookeeperMetadataStore.class)
public static TestingServer zookeeperTestingServer() throws Exception {
TestingServer testingServer = new TestingServer(true);
System.setProperty("metadata.store.zookeeper.connect-string", testingServer.getConnectString());
System.setProperty("metadata.store.zookeeper.encoding", StandardCharsets.US_ASCII.name());
return testingServer;
}
@Configuration
@ConditionalOnClass(DynamoDbMetadataStore.class)
protected static class DynamoDbMockConfig {
@Bean
public static AmazonDynamoDBAsync dynamoDB() {
AmazonDynamoDBAsync dynamoDb = mock(AmazonDynamoDBAsync.class);
willReturn(new DescribeTableResult())
.given(dynamoDb)
.describeTable(any(DescribeTableRequest.class));
return dynamoDb;
}
@Bean
public static AWSCredentialsProvider awsCredentialsProvider() {
return mock(AWSCredentialsProvider.class);
}
@Bean
public static RegionProvider regionProvider() {
return mock(RegionProvider.class);
}
}
}
}

View File

@@ -0,0 +1,35 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-micrometer-common</artifactId>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-test-support</artifactId>
<scope>test</scope>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2018 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.stream.app.micrometer.common;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* Micrometer common tags for Cloud Foundry deployment properties. Based on the CF application environment variables:
* https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html
*
* Tags are set only if the "cloud" Spring profile is set. The "cloud" profile is activated automatically when an
* application is deployed in CF: https://docs.cloudfoundry.org/buildpacks/java/configuring-service-connections/spring-service-bindings.html#cloud-profiles
*
* Use the spring.cloud.stream.app.metrics.cf.tags.enabled=false property to disable inserting those tags.
*
* @author Christian Tzolov
*/
@Configuration
@Profile("cloud")
@ConditionalOnProperty(name = "spring.cloud.stream.app.metrics.cf.tags.enabled", havingValue = "true", matchIfMissing = true)
public class CloudFoundryMicrometerCommonTags {
@Value("${vcap.application.org_name:default}")
private String organizationName;
@Value("${vcap.application.space_id:unknown}")
private String spaceId;
@Value("${vcap.application.space_name:unknown}")
private String spaceName;
@Value("${vcap.application.application_name:unknown}")
private String applicationName;
@Value("${vcap.application.application_id:unknown}")
private String applicationId;
@Value("${vcap.application.application_version:unknown}")
private String applicationVersion;
@Value("${vcap.application.instance_index:0}")
private String instanceIndex;
@Bean
public MeterRegistryCustomizer<MeterRegistry> cloudFoundryMetricsCommonTags() {
return registry -> registry.config()
.commonTags("cf.org.name", organizationName)
.commonTags("cf.space.id", spaceId)
.commonTags("cf.space.name", spaceName)
.commonTags("cf.app.id", applicationId)
.commonTags("cf.app.name", applicationName)
.commonTags("cf.app.version", applicationVersion)
.commonTags("cf.instance.index", instanceIndex);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2018 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.stream.app.micrometer.common;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Auto configuration extends the micrometer metrics with additional tags such as: stream name, application name,
* instance index and guids. Later are necessary to allow discrimination and aggregation of app metrics by external
* metrics collection and visualizaiton tools.
*
* Use the spring.cloud.stream.app.metrics.common.tags.enabled=false property to disable inserting those tags.
*
* @author Christian Tzolov
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.stream.app.metrics.common.tags.enabled", havingValue = "true", matchIfMissing = true)
public class SpringCloudStreamMicrometerCommonTags {
@Value("${spring.cloud.dataflow.stream.name:unknown}")
private String streamName;
@Value("${spring.cloud.dataflow.stream.app.label:unknown}")
private String applicationName;
@Value("${spring.cloud.stream.instanceIndex:0}")
private String instanceIndex;
@Value("${spring.cloud.application.guid:unknown}")
private String applicationGuid;
@Value("${spring.cloud.dataflow.stream.app.type:unknown}")
private String applicationType;
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("stream.name", streamName)
.commonTags("application.name", applicationName)
.commonTags("application.type", applicationType)
.commonTags("instance.index", instanceIndex)
.commonTags("application.guid", applicationGuid);
}
@Bean
public MeterRegistryCustomizer<MeterRegistry> renameNameTag() {
return registry -> {
if (registry.getClass().getCanonicalName().contains("AtlasMeterRegistry")) {
registry.config().meterFilter(MeterFilter.renameTag("spring.integration", "name", "aname"));
}
if (registry.getClass().getCanonicalName().contains("InfluxMeterRegistry")) {
registry.config().meterFilter(MeterFilter.replaceTagValues("application.name",
tagValue -> ("time".equalsIgnoreCase(tagValue)) ? "atime" : tagValue));
}
};
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2018 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.stream.app.micrometer.common;
import java.util.Properties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
/**
* Disables all Micrometer Repositories added as App Starters dependencies by default.
* That means disabling Datadog, Influx and Prometheus.
*
* @author Christian Tzolov
*/
public class SpringCloudStreamMicrometerEnvironmentPostProcessor implements EnvironmentPostProcessor {
protected static final String PROPERTY_SOURCE_KEY_NAME = SpringCloudStreamMicrometerEnvironmentPostProcessor.class.getName();
private final static String METRICS_PROPERTY_NAME_TEMPLATE = "management.metrics.export.%s.enabled";
private final static String[] METRICS_REPOSITORY_NAMES =
new String[] { "datadog", "influx", "prometheus", "prometheus.rsocket" };
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties properties = new Properties();
if (!environment.containsProperty("management.endpoints.web.exposure.include")) {
properties.setProperty("management.endpoints.web.exposure.include", "prometheus");
}
for (String metricsRepositoryName : METRICS_REPOSITORY_NAMES) {
String propertyKey = String.format(METRICS_PROPERTY_NAME_TEMPLATE, metricsRepositoryName);
// Back off if the property is already set.
if (!environment.containsProperty(propertyKey)) {
properties.setProperty(propertyKey, "false");
}
}
// This post-processor is called multiple times but sets the properties only once.
if (!properties.isEmpty()) {
PropertiesPropertySource propertiesPropertySource =
new PropertiesPropertySource(PROPERTY_SOURCE_KEY_NAME, properties);
environment.getPropertySources().addLast(propertiesPropertySource);
}
}
}

View File

@@ -0,0 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.stream.app.micrometer.common.SpringCloudStreamMicrometerCommonTags,\
org.springframework.cloud.stream.app.micrometer.common.CloudFoundryMicrometerCommonTags
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.stream.app.micrometer.common.SpringCloudStreamMicrometerEnvironmentPostProcessor

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2018-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.stream.app.micrometer.common;
import java.io.IOException;
import java.nio.charset.Charset;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.pivotal.cfenv.test.CfEnvTestUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimplePropertiesConfigAdapter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.assertNotNull;
/**
* @author Christian Tzolov
* @author Soby Chacko
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AbstractMicrometerTagTest.AutoConfigurationApplication.class)
public class AbstractMicrometerTagTest {
@Autowired
protected SimpleMeterRegistry simpleMeterRegistry;
@Autowired
protected ConfigurableApplicationContext context;
protected Meter meter;
@Before
public void before() {
assertNotNull(simpleMeterRegistry);
meter = simpleMeterRegistry.find("jvm.memory.committed").meter();
assertNotNull("The jvm.memory.committed meter mast be present in SpringBoot apps!", meter);
}
@BeforeClass
public static void setup() throws IOException {
String serviceJson = StreamUtils.copyToString(new DefaultResourceLoader().getResource(
"classpath:/org/springframework/cloud/stream/app/micrometer/common/pcf-scs-info.json")
.getInputStream(), Charset.forName("UTF-8"));
CfEnvTestUtils.mockVcapServicesFromString(serviceJson);
}
@SpringBootApplication
@EnableConfigurationProperties(SimpleProperties.class)
public static class AutoConfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoConfigurationApplication.class, args);
}
@Bean
public SimpleMeterRegistry simpleMeterRegistry(SimpleConfig config, Clock clock) {
return new SimpleMeterRegistry(config, clock);
}
@Bean
@ConditionalOnMissingBean
public SimpleConfig simpleConfig(SimpleProperties simpleProperties) {
return new SimplePropertiesConfigAdapter(simpleProperties);
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2018 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.stream.app.micrometer.common;
import org.hamcrest.Matchers;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* @author Christian Tzolov
*/
@RunWith(Enclosed.class)
public class CloudFoundryMicrometerCommonTagsTest {
@ActiveProfiles("cloud")
public static class ActiveCloudProfileDefaultValues extends AbstractMicrometerTagTest {
@Test
public void testDefaultTagValues() {
assertThat(meter.getId().getTag("cf.org.name"), is("default"));
assertThat(meter.getId().getTag("cf.space.id"), is("unknown"));
assertThat(meter.getId().getTag("cf.space.name"), is("unknown"));
assertThat(meter.getId().getTag("cf.app.name"), is("unknown"));
assertThat(meter.getId().getTag("cf.app.id"), is("unknown"));
assertThat(meter.getId().getTag("cf.app.version"), is("unknown"));
assertThat(meter.getId().getTag("cf.instance.index"), is("0"));
}
}
@TestPropertySource(properties = {
"vcap.application.org_name=PivotalOrg",
"vcap.application.space_id=SpringSpaceId",
"vcap.application.space_name=SpringSpace",
"vcap.application.application_name=App666",
"vcap.application.application_id=666guid",
"vcap.application.application_version=2.0",
"vcap.application.instance_index=123" })
@ActiveProfiles("cloud")
public static class ActiveCloudProfile extends AbstractMicrometerTagTest {
@Test
public void testPresetTagValues() {
assertThat(meter.getId().getTag("cf.org.name"), is("PivotalOrg"));
assertThat(meter.getId().getTag("cf.space.id"), is("SpringSpaceId"));
assertThat(meter.getId().getTag("cf.space.name"), is("SpringSpace"));
assertThat(meter.getId().getTag("cf.app.name"), is("App666"));
assertThat(meter.getId().getTag("cf.app.id"), is("666guid"));
assertThat(meter.getId().getTag("cf.app.version"), is("2.0"));
assertThat(meter.getId().getTag("cf.instance.index"), is("123"));
}
}
@TestPropertySource(properties = {
"vcap.application.org_name=PivotalOrg",
"vcap.application.space_id=SpringSpaceId",
"vcap.application.space_name=SpringSpace",
"vcap.application.application_name=App666",
"vcap.application.application_id=666guid",
"vcap.application.application_version=2.0",
"vcap.application.instance_index=123" })
public static class InactiveCloudProfile extends AbstractMicrometerTagTest {
@Test
public void testDisabledTagValues() {
assertThat(meter.getId().getTag("cf.org.name"), is(Matchers.nullValue()));
assertThat(meter.getId().getTag("cf.space.id"), is(Matchers.nullValue()));
assertThat(meter.getId().getTag("cf.space.name"), is(Matchers.nullValue()));
assertThat(meter.getId().getTag("cf.app.name"), is(Matchers.nullValue()));
assertThat(meter.getId().getTag("cf.app.id"), is(Matchers.nullValue()));
assertThat(meter.getId().getTag("cf.app.version"), is(Matchers.nullValue()));
assertThat(meter.getId().getTag("cf.instance.index"), is(Matchers.nullValue()));
}
}
@TestPropertySource(properties = { "spring.cloud.stream.app.metrics.cf.tags.enabled=false" })
@ActiveProfiles("cloud")
public static class ActiveCloudProfileDisabledProperty extends InactiveCloudProfile {
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 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.stream.app.micrometer.common;
import io.micrometer.core.instrument.Meter;
import io.micrometer.influx.InfluxMeterRegistry;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
/**
* @author Christian Tzolov
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = InfluxReservedKeywordHandlingTest.AutoConfigurationApplication.class,
properties = {
"management.metrics.export.influx.enabled=true",
"spring.cloud.dataflow.stream.app.label=time" })
public class InfluxReservedKeywordHandlingTest {
@Autowired
protected InfluxMeterRegistry influxMeterRegistry;
@Test
public void testPresetTagValues() {
assertNotNull(influxMeterRegistry);
Meter meter = influxMeterRegistry.find("jvm.memory.committed").meter();
assertNotNull("The jvm.memory.committed meter mast be present in SpringBoot apps!", meter);
assertThat(meter.getId().getTag("application.name"), is("atime"));
}
@SpringBootApplication
public static class AutoConfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoConfigurationApplication.class, args);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2018 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.stream.app.micrometer.common;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.test.context.TestPropertySource;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* @author Christian Tzolov
*/
@RunWith(Enclosed.class)
public class SpringCloudStreamMicrometerCommonTagsTest {
public static class TestDefaultTagValues extends AbstractMicrometerTagTest {
@Test
public void testDefaultTagValues() {
assertThat(meter.getId().getTag("stream.name"), is("unknown"));
assertThat(meter.getId().getTag("application.name"), is("unknown"));
assertThat(meter.getId().getTag("instance.index"), is("0"));
assertThat(meter.getId().getTag("application.type"), is("unknown"));
assertThat(meter.getId().getTag("application.guid"), is("unknown"));
}
}
@TestPropertySource(properties = {
"spring.cloud.dataflow.stream.name=myStream",
"spring.cloud.dataflow.stream.app.label=myApp",
"spring.cloud.stream.instanceIndex=666",
"spring.cloud.application.guid=666guid",
"spring.cloud.dataflow.stream.app.type=source" })
public static class TestPresetTagValues extends AbstractMicrometerTagTest {
@Test
public void testPresetTagValues() {
assertThat(meter.getId().getTag("stream.name"), is("myStream"));
assertThat(meter.getId().getTag("application.name"), is("myApp"));
assertThat(meter.getId().getTag("instance.index"), is("666"));
assertThat(meter.getId().getTag("application.type"), is("source"));
assertThat(meter.getId().getTag("application.guid"), is("666guid"));
}
}
@TestPropertySource(properties = { "spring.cloud.stream.app.metrics.common.tags.enabled=false" })
public static class TestDisabledTagValues extends AbstractMicrometerTagTest {
@Test
public void testDefaultTagValues() {
assertThat(meter.getId().getTag("stream.name"), is(nullValue()));
assertThat(meter.getId().getTag("application.name"), is(nullValue()));
assertThat(meter.getId().getTag("instance.index"), is(nullValue()));
assertThat(meter.getId().getTag("application.type"), is(nullValue()));
assertThat(meter.getId().getTag("application.guid"), is(nullValue()));
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2018 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.stream.app.micrometer.common;
import org.hamcrest.core.Is;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.core.env.PropertySource;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
/**
* @author Christian Tzolov
*/
@RunWith(Enclosed.class)
public class SpringCloudStreamMicrometerEnvironmentPostProcessorTest {
public static class TestDefaultMetricsEnabledProperties extends AbstractMicrometerTagTest {
@Test
public void testDefaultProperties() {
assertNotNull(context);
PropertySource propertySource = context.getEnvironment().getPropertySources()
.get(SpringCloudStreamMicrometerEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME);
assertNotNull("Property source "
+ SpringCloudStreamMicrometerEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME + " is null",
propertySource);
assertThat(propertySource.getProperty("management.metrics.export.influx.enabled"), Is.is("false"));
assertThat(propertySource.getProperty("management.metrics.export.prometheus.enabled"), Is.is("false"));
assertThat(propertySource.getProperty("management.metrics.export.prometheus.rsocket.enabled"), Is.is("false"));
assertThat(propertySource.getProperty("management.metrics.export.datadog.enabled"), Is.is("false"));
assertThat(propertySource.getProperty("management.endpoints.web.exposure.include"), Is.is("prometheus"));
assertThat(context.getEnvironment().getProperty("management.metrics.export.influx.enabled"), Is.is("false"));
assertThat(context.getEnvironment().getProperty("management.metrics.export.prometheus.enabled"), Is.is("false"));
assertThat(context.getEnvironment().getProperty("management.metrics.export.datadog.enabled"), Is.is("false"));
assertThat(context.getEnvironment().getProperty("management.endpoints.web.exposure.include"), Is.is("prometheus"));
}
}
@TestPropertySource(properties = {
"management.metrics.export.simple.enabled=true",
"management.metrics.export.influx.enabled=true",
"management.metrics.export.prometheus.enabled=true",
"management.metrics.export.prometheus.rsocket.enabled=true",
"management.metrics.export.datadog.enabled=true",
"management.endpoints.web.exposure.include=info,health"})
public static class TestOverrideMetricsEnabledProperties extends AbstractMicrometerTagTest {
@Test
public void testOverrideProperties() {
assertNotNull(context);
PropertySource propertySource = context.getEnvironment().getPropertySources()
.get(SpringCloudStreamMicrometerEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME);
assertNull("Property source "
+ SpringCloudStreamMicrometerEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME + " is not null",
propertySource);
assertThat(context.getEnvironment().getProperty("management.metrics.export.influx.enabled"), Is.is("true"));
assertThat(context.getEnvironment().getProperty("management.metrics.export.prometheus.enabled"), Is.is("true"));
assertThat(context.getEnvironment().getProperty("management.metrics.export.prometheus.rsocket.enabled"), Is.is("true"));
assertThat(context.getEnvironment().getProperty("management.metrics.export.datadog.enabled"), Is.is("true"));
assertThat(context.getEnvironment().getProperty("management.endpoints.web.exposure.include"), Is.is("info,health"));
}
}
}

View File

@@ -0,0 +1,13 @@
{
"sso":[{
"name": "sso",
"label": "sso",
"plan": "notfree",
"tags": ["configuration"],
"credentials":{
"uri": "https://pivotal.io",
"client_id": "fakeClientId",
"client_secret": "fakeSecret",
"access_token_uri": "token"
}
}]}

View File

@@ -0,0 +1,22 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-postprocessor-common</artifactId>
<name>stream-apps-postprocessor-common</name>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2018 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.stream.app.postprocessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* An {@link EnvironmentPostProcessor} to set the {@code spring.cloud.stream.bindings.{input,output}.contentType}
* channel properties to a default of {@code application/octet-stream} if it has not been set already.
*
* Subclasses may extend this class to change the default content type and channel name(s).
*
* @author Chris Schaefer
*/
public class ContentTypeEnvironmentPostProcessor implements EnvironmentPostProcessor {
private Map<String, String> channelMap = createChannelMap();
private Map<String, String> createChannelMap() {
Map<String, String> channelMap = new HashMap<>();
channelMap.put(Sink.INPUT, "application/octet-stream");
channelMap.put(Source.OUTPUT, "application/octet-stream");
return channelMap;
}
protected static final String PROPERTY_SOURCE_KEY_NAME = ContentTypeEnvironmentPostProcessor.class.getName();
protected static final String CONTENT_TYPE_PROPERTY_PREFIX = "spring.cloud.stream.bindings.";
protected static final String CONTENT_TYPE_PROPERTY_SUFFIX = ".contentType";
public ContentTypeEnvironmentPostProcessor() {
super();
}
protected ContentTypeEnvironmentPostProcessor(Map<String, String> channelMap) {
this.channelMap = channelMap;
}
protected ContentTypeEnvironmentPostProcessor(String contentType) {
for (Map.Entry<String, String> channel : channelMap.entrySet()) {
channel.setValue(contentType);
}
}
protected ContentTypeEnvironmentPostProcessor(String channelName, String contentType) {
channelMap.put(channelName, contentType);
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
Properties properties = new Properties();
for (Map.Entry<String, String> channel : channelMap.entrySet()) {
String propertyKey = CONTENT_TYPE_PROPERTY_PREFIX + channel.getKey() + CONTENT_TYPE_PROPERTY_SUFFIX;
if (!configurableEnvironment.containsProperty(propertyKey)) {
properties.setProperty(propertyKey, channel.getValue());
}
}
if (!properties.isEmpty()) {
PropertiesPropertySource propertiesPropertySource =
new PropertiesPropertySource(PROPERTY_SOURCE_KEY_NAME, properties);
configurableEnvironment.getPropertySources().addLast(propertiesPropertySource);
}
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2018 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.stream.app.postprocessor;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Test cases for {@ContentTypeEnvironmentPostProcessor}.
*
* @author Chris Schaefer
*/
public class ContentTypeEnvironmentPostProcessorTests {
@Test
public void testPostProcessorDefaults() {
ConfigurableEnvironment configurableEnvironment = getEnvironment();
PropertySource propertySource = configurableEnvironment.getPropertySources()
.get(ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME);
assertNotNull("Property source " + ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME + " is null",
propertySource);
assertTrue("Unexpected input content type", propertySource.getProperty(getContentTypeProperty(Sink.INPUT))
.equals("application/octet-stream"));
assertTrue("Unexpected output content type", propertySource.getProperty(getContentTypeProperty(Source.OUTPUT))
.equals("application/octet-stream"));
}
@Test
public void testUserDefinedOutputContentType() {
PropertiesPropertySource testProperties = buildTestProperties(Source.OUTPUT, "text/plain");
ConfigurableEnvironment configurableEnvironment = getEnvironment(testProperties);
assertTrue("Output contentType property key not found",
configurableEnvironment.containsProperty(getContentTypeProperty(Source.OUTPUT)));
assertTrue("Unexpected output content type", configurableEnvironment.getProperty(getContentTypeProperty(Source.OUTPUT))
.equals("text/plain"));
}
@Test
public void testUserDefinedInputContentType() {
PropertiesPropertySource testProperties = buildTestProperties(Sink.INPUT, "text/html");
ConfigurableEnvironment configurableEnvironment = getEnvironment(testProperties);
assertTrue("Input contentType property key not found",
configurableEnvironment.containsProperty(getContentTypeProperty(Sink.INPUT)));
assertTrue("Unexpected input content type", configurableEnvironment.getProperty(getContentTypeProperty(Sink.INPUT))
.equals("text/html"));
}
@Test
public void testConfigureCustomChannel() {
ConfigurableEnvironment configurableEnvironment = getEnvironment(new CustomChannel());
PropertySource propertySource = configurableEnvironment.getPropertySources()
.get(ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME);
assertNotNull("Property source " + ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME + " is null",
propertySource);
assertTrue("myChannelName contentType property key not found",
propertySource.containsProperty(getContentTypeProperty("myChannelName")));
assertTrue("Unexpected myChannelName content type", propertySource.getProperty(getContentTypeProperty("myChannelName"))
.equals("application/octet-stream"));
}
public static class CustomChannel extends ContentTypeEnvironmentPostProcessor {
private static final String CHANNEL_NAME = "myChannelName";
private static final String CONTENT_TYPE = "application/octet-stream";
public CustomChannel() {
super(CHANNEL_NAME, CONTENT_TYPE);
}
}
@Test
public void testConfigureDefaultChannelsCustomContentType() {
ConfigurableEnvironment configurableEnvironment = getEnvironment(new DefaultChannelsCustomContentTypes());
PropertySource propertySource = configurableEnvironment.getPropertySources()
.get(ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME);
assertNotNull("Property source " + ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME + " is null",
propertySource);
assertTrue("Output contentType property key not found",
propertySource.containsProperty(getContentTypeProperty(Source.OUTPUT)));
assertTrue("Unexpected output content type", propertySource.getProperty(getContentTypeProperty(Source.OUTPUT))
.equals("image/jpeg"));
assertTrue("Input contentType property key not found",
propertySource.containsProperty(getContentTypeProperty(Sink.INPUT)));
assertTrue("Unexpected input content type", propertySource.getProperty(getContentTypeProperty(Sink.INPUT))
.equals("image/gif"));
}
@Test
public void testConfigureDefaultChannelsSameContentType() {
ConfigurableEnvironment configurableEnvironment = getEnvironment(new ChannelSameContentType());
PropertySource propertySource = configurableEnvironment.getPropertySources()
.get(ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME);
assertNotNull("Property source " + ContentTypeEnvironmentPostProcessor.PROPERTY_SOURCE_KEY_NAME + " is null",
propertySource);
assertTrue("Output contentType property key not found",
propertySource.containsProperty(getContentTypeProperty(Source.OUTPUT)));
assertTrue("Unexpected output content type", propertySource.getProperty(getContentTypeProperty(Source.OUTPUT))
.equals("image/jpeg"));
assertTrue("Input contentType property key not found",
propertySource.containsProperty(getContentTypeProperty(Sink.INPUT)));
assertTrue("Unexpected input content type", propertySource.getProperty(getContentTypeProperty(Sink.INPUT))
.equals("image/jpeg"));
}
public static class ChannelSameContentType extends ContentTypeEnvironmentPostProcessor {
private static final String CONTENT_TYPE = "image/jpeg";
public ChannelSameContentType() {
super(CONTENT_TYPE);
}
}
public static class DefaultChannelsCustomContentTypes extends ContentTypeEnvironmentPostProcessor {
private static Map<String, String> CHANNEL_MAP = createChannelMap();
private static Map<String, String> createChannelMap() {
Map<String, String> channelMap = new HashMap<>();
channelMap.put(Source.OUTPUT, "image/jpeg");
channelMap.put(Sink.INPUT, "image/gif");
return channelMap;
}
public DefaultChannelsCustomContentTypes() {
super(CHANNEL_MAP);
}
}
private static String getContentTypeProperty(String channelName) {
return ContentTypeEnvironmentPostProcessor.CONTENT_TYPE_PROPERTY_PREFIX + channelName
+ ContentTypeEnvironmentPostProcessor.CONTENT_TYPE_PROPERTY_SUFFIX;
}
private PropertiesPropertySource buildTestProperties(String channelName, String contentType) {
Properties testProperties = new Properties();
testProperties.setProperty(getContentTypeProperty(channelName), contentType);
return new PropertiesPropertySource("test-properties", testProperties);
}
private ConfigurableEnvironment getEnvironment() {
return getEnvironment(null, null);
}
private ConfigurableEnvironment getEnvironment(PropertiesPropertySource propertiesPropertySource) {
return getEnvironment(propertiesPropertySource, null);
}
private ConfigurableEnvironment getEnvironment(EnvironmentPostProcessor environmentPostProcessor) {
return getEnvironment(null, environmentPostProcessor);
}
private ConfigurableEnvironment getEnvironment(PropertiesPropertySource propertiesPropertySource,
EnvironmentPostProcessor environmentPostProcessor) {
SpringApplication springApplication = new SpringApplicationBuilder()
.sources(ContentTypeEnvironmentPostProcessorTests.class)
.web(WebApplicationType.NONE).build();
ConfigurableApplicationContext context = springApplication.run();
if (propertiesPropertySource != null) {
context.getEnvironment().getPropertySources().addFirst(propertiesPropertySource);
}
if (environmentPostProcessor == null) {
environmentPostProcessor = new ContentTypeEnvironmentPostProcessor();
}
environmentPostProcessor.postProcessEnvironment(context.getEnvironment(), springApplication);
ConfigurableEnvironment configurableEnvironment = context.getEnvironment();
context.close();
return configurableEnvironment;
}
}

View File

@@ -0,0 +1,33 @@
=== `App Starters Security` Common Module
Spring Boot auto-configuration to manage the web security of the application starters.
When the `app-starters-security-common` dependency is on the classpath, the `spring.cloud.streamapp.security.enabled` and `spring.cloud.streamapp.security.csrf-enabled` properties control the application security behavior.
By default the security is enabled allowing unauthorized access only to the `Info` and `Health` endpoints.
The `spring.cloud.streamapp.security.enabled = false` completely surpass the application security.
For secured application setting `spring.cloud.streamapp.security.csrf-enabled = false` disables security for the CSRF access.
With security enabled (`spring.cloud.streamapp.security.enabled = true`) and `actuator` dependency on the classpath, the `(Reactive)ManagementWebSecurityAutoConfiguration` is activated, providing unauthenticated access to the `HealthEndpoint` and `InfoEndpoint`.
If the user specifies their own `WebSecurityConfigurerAdapter` (for MVC application), this configuration will back-off completely and the user should specify all the bits that they want to configure as part of the custom security configuration.
For reactive (WebFlux) application the same effect can be achieved with a custom `WebFilterChainProxy` bean.
=== Configuration
To include app starters security management for a stream app, just include a dependency on this module.
[source,xml]
----
<dependency>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>app-starters-security-common</artifactId>
</dependency>
----
All Spring Cloud Stream app starters that inherit form the `core` pom have the `app-starters-security-common` dependency included by default.
* `spring.cloud.streamapp.security.enabled` (default: `true`). If set to `false` it surpasses the boot security.
* `spring.cloud.streamapp.security.csrf-enabled` (default: `true`). If set to `false`, for secured applications it enables CQRS.

View File

@@ -0,0 +1,52 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-security-common</artifactId>
<dependencies>
<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-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,66 @@
/*
* Copyright 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.stream.app.security.common;
import org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import reactor.core.publisher.Flux;
/**
* @author Artem Bilan
*
* @since 3.0
*/
@Conditional(OnHttpCsrfOrSecurityDisabled.class)
@Configuration
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, WebFluxConfigurer.class })
@ConditionalOnMissingBean(WebFilterChainProxy.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@AutoConfigureBefore(value = { ReactiveManagementWebSecurityAutoConfiguration.class,
ReactiveSecurityAutoConfiguration.class })
@EnableConfigurationProperties(AppStarterWebSecurityAutoConfigurationProperties.class)
public class AppStarterWebFluxSecurityAutoConfiguration {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
AppStarterWebSecurityAutoConfigurationProperties securityProperties) {
if (!securityProperties.isCsrfEnabled()) {
http.csrf().disable();
}
if (!securityProperties.isEnabled()) {
http.authorizeExchange()
.anyExchange()
.permitAll();
}
return http.build();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2018-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.stream.app.security.common;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 2.1
*/
@Conditional(OnHttpCsrfOrSecurityDisabled.class)
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@AutoConfigureBefore(value = { ManagementWebSecurityAutoConfiguration.class, SecurityAutoConfiguration.class })
@EnableConfigurationProperties(AppStarterWebSecurityAutoConfigurationProperties.class)
@EnableWebSecurity
public class AppStarterWebSecurityAutoConfiguration {
@Bean
WebSecurityConfigurerAdapter appStarterWebSecurityConfigurerAdapter(
AppStarterWebSecurityAutoConfigurationProperties securityProperties) {
return new WebSecurityConfigurerAdapter() {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
if (!securityProperties.isCsrfEnabled()) {
http.csrf().disable();
}
}
@Override
public void configure(WebSecurity builder) {
if (!securityProperties.isEnabled()) {
builder.ignoring().antMatchers("/**");
}
}
};
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2018 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.stream.app.security.common;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* {@code AppStarterWebSecurityAutoConfiguration} properties.
*
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 2.1
*/
@ConfigurationProperties("spring.cloud.streamapp.security")
public class AppStarterWebSecurityAutoConfigurationProperties {
/**
* The security enabling flag.
*/
private boolean enabled = true;
/**
* The security CSRF enabling flag. Makes sense only if security 'enabled` is `true'.
*/
private boolean csrfEnabled = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isCsrfEnabled() {
return this.csrfEnabled;
}
public void setCsrfEnabled(boolean csrfEnabled) {
this.csrfEnabled = csrfEnabled;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 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.stream.app.security.common;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* An {@link AnyNestedCondition} to enable app starters-specific security auto-configuration
* overriding out-of-the-box one in Spring Boot.
*
* @author Artem Bilan
*
* @since 3.0
*/
class OnHttpCsrfOrSecurityDisabled extends AnyNestedCondition {
OnHttpCsrfOrSecurityDisabled() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(name = "spring.cloud.streamapp.security.enabled", havingValue = "false")
static class SecurityDisabled {
}
@ConditionalOnProperty(name = "spring.cloud.streamapp.security.csrf-enabled", havingValue = "false")
static class HttpCsrfDisabled {
}
}

View File

@@ -0,0 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.stream.app.security.common.AppStarterWebSecurityAutoConfiguration,\
org.springframework.cloud.stream.app.security.common.AppStarterWebFluxSecurityAutoConfiguration

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2018-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.stream.app.security.common;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.annotation.DirtiesContext;
/**
* @author Christian Tzolov
* @author Artem Bilan
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext
public abstract class AbstractSecurityCommonTests {
@Autowired
protected TestRestTemplate restTemplate;
@SpringBootApplication
public static class AutoConfigurationApplication {
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
/**
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=reactive",
"spring.cloud.streamapp.security.enabled=false",
"management.endpoints.web.exposure.include=health,info,env",
"info.name=MY TEST APP" })
public class ReactiveSecurityDisabledManagementSecurityEnabledTests extends AbstractSecurityCommonTests {
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("UP", health.get("status"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("MY TEST APP", info.get("name"));
}
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=reactive",
"org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration"
+ ",org.springframework.cloud.stream.app.security.common.AppStarterWebFluxSecurityAutoConfiguration",
"management.endpoints.web.exposure.include=health,info,env",
"info.name=MY TEST APP" })
public class ReactiveSecurityEnabledManagementSecurityDisabledAuthorizedAccessTests extends AbstractSecurityCommonTests {
@Autowired
private SecurityProperties securityProperties;
@BeforeEach
public void before() {
restTemplate.getRestTemplate().getInterceptors().add(new BasicAuthenticationInterceptor(
securityProperties.getUser().getName(), securityProperties.getUser().getPassword()));
}
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("UP", health.get("status"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("MY TEST APP", info.get("name"));
}
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=reactive",
"spring.autoconfigure.exclude=" +
"org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration"
+ ",org.springframework.cloud.stream.app.security.common.AppStarterWebFluxSecurityAutoConfiguration",
"management.endpoints.web.exposure.include=health,info" })
public class ReactiveSecurityEnabledManagementSecurityDisabledUnauthorizedAccessTests extends AbstractSecurityCommonTests {
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=reactive",
"management.endpoints.web.exposure.include=health,info,env",
"info.name=MY TEST APP" })
public class ReactiveSecurityEnabledManagementSecurityEnabledTests extends AbstractSecurityCommonTests {
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("UP", health.get("status"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("MY TEST APP", info.get("name"));
}
// The ManagementWebSecurityAutoConfiguration exposes only Info and Health endpoint not Env!
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=servlet",
"spring.cloud.streamapp.security.enabled=false",
"management.endpoints.web.exposure.include=health,info,env",
"info.name=MY TEST APP" })
public class SecurityDisabledManagementSecurityEnabledTests extends AbstractSecurityCommonTests {
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("UP", health.get("status"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("MY TEST APP", info.get("name"));
}
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=servlet",
"spring.autoconfigure.exclude=org.springframework.boot.actuate.autoconfigure.security.servlet" +
".ManagementWebSecurityAutoConfiguration"
+ ",org.springframework.cloud.stream.app.security.common.AppStarterWebSecurityAutoConfiguration",
"management.endpoints.web.exposure.include=health,info,env",
"info.name=MY TEST APP" })
public class SecurityEnabledManagementSecurityDisabledAuthorizedAccessTests extends AbstractSecurityCommonTests {
@Autowired
private SecurityProperties securityProperties;
@BeforeEach
public void before() {
restTemplate.getRestTemplate().getInterceptors().add(new BasicAuthenticationInterceptor(
securityProperties.getUser().getName(), securityProperties.getUser().getPassword()));
}
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("UP", health.get("status"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("MY TEST APP", info.get("name"));
}
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=servlet",
"spring.autoconfigure.exclude=" +
"org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration"
+ ",org.springframework.cloud.stream.app.security.common.AppStarterWebSecurityAutoConfiguration",
"management.endpoints.web.exposure.include=health,info" })
public class SecurityEnabledManagementSecurityDisabledUnauthorizedAccessTests extends AbstractSecurityCommonTests {
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("Unauthorized", health.get("error"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("Unauthorized", info.get("error"));
}
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
assertTrue(response.hasBody());
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 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.stream.app.security.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
/**
* @author Christian Tzolov
* @author Artem Bilan
*
* @since 3.0
*/
@TestPropertySource(properties = {
"spring.main.web-application-type=servlet",
"management.endpoints.web.exposure.include=health,info,env",
"info.name=MY TEST APP" })
public class SecurityEnabledManagementSecurityEnabledTests extends AbstractSecurityCommonTests {
@Test
@SuppressWarnings("rawtypes")
public void testHealthEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/health", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map health = response.getBody();
assertEquals("UP", health.get("status"));
}
@Test
@SuppressWarnings("rawtypes")
public void testInfoEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/info", Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
Map info = response.getBody();
assertEquals("MY TEST APP", info.get("name"));
}
// The ManagementWebSecurityAutoConfiguration exposes only Info and Health endpoint not Env!
@Test
@SuppressWarnings("rawtypes")
public void testEnvEndpoint() {
ResponseEntity<Map> response = this.restTemplate.getForEntity("/actuator/env", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
assertTrue(response.hasBody());
}
}

View File

@@ -0,0 +1,52 @@
=== `Data Flow Task Launch Request Support` Common Module
This artifact contains a Spring Boot auto-configuration providing components to produce a Task Launch Request payload.
In theory any message source can be used to launch a Task. The task launch request is compatible with the
task-launcher-dataflow sink.
==== Data Flow Task Launch Request
Data Flow Task Launch Request requests require a Data Flow server with an existing task definition.
The task launch request contains the name of the task to launch. This must the name of a defined task in Data Flow.
You may optionally provide command line arguments and deployment properties.
===== Task Name
The task name is a required field. This may be statically configured by setting `task.launch.request.task-name`,
extracted from the Message by setting `task.launch.request.task-name-expression`.
You may also override the default implementation of the `TaskNameMessageMapper` bean to enable more complex runtime task name mappings.
===== Task Command Line Arguments
The launched task often requires additional data which may be passed as command line arguments.
The `task.launch.request.args` property accepts a comma delimited string of key-value pairs, for example
`key1=val1,key2=val2`. In addition, a `task.launch.request.arg-expressions` allows you to use SpEL expressions to evaluate
message contents to provide command line arguments.
For example, `task.launch.request.arg-expressions=foo=payload.toUpperCase(),bar=payload.substring(0,2)`.
You may also provide override implementation of the `CommandLineArgumentsMessageMapper` bean to implement more complex logic.
===== Task Deployment Properties
Deployment properties are platform-specific configuration used by the `TaskLauncher` and are always statically configured by
setting `task.launch.request.deployment-properties` and apply to every task launch request.
=== Configuration
To enable any stream app to transform its output to a Task Launch Request, include a dependency on this module
[source,xml]
----
<dependency>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>app-starters-task-launch-request-common</artifactId>
</dependency>
----
Setting the application property `spring.cloud.stream.function.definition=taskLaunchRequest` is required to execute the transformation.
You may safely add the above dependency to applications which optionally produce a task launch request.
`TaskLaunchRequestIntegrationTests` provides some configuration examples.

View File

@@ -0,0 +1,45 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-task-launch-request-common</artifactId>
<name>stream-apps-task-launch-request-common</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>stream-apps-file-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,64 @@
/*
* Copyright 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.stream.app.tasklaunchrequest;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DataFlowTaskLaunchRequest {
@JsonProperty("args")
private List<String> commandlineArguments = new ArrayList<>();
@JsonProperty("deploymentProps")
private Map<String, String> deploymentProperties = new HashMap<>();
@JsonProperty("name")
private String taskName;
public void setCommandlineArguments(List<String> commandlineArguments) {
this.commandlineArguments = new ArrayList<>(commandlineArguments);
}
public List<String> getCommandlineArguments() {
return this.commandlineArguments;
}
public void setDeploymentProperties(Map<String, String> deploymentProperties) {
this.deploymentProperties = deploymentProperties;
}
public Map<String, String> getDeploymentProperties() {
return this.deploymentProperties;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskName() {
return this.taskName;
}
public DataFlowTaskLaunchRequest addCommmandLineArguments(Collection<String> args) {
this.commandlineArguments.addAll(args);
return this;
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2018-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.stream.app.tasklaunchrequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.CommandLineArgumentsMessageMapper;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.TaskLaunchRequestSupplier;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.TaskNameMessageMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.messaging.Message;
import org.springframework.util.StringUtils;
/**
* @author David Turanski
**/
@Configuration
@EnableConfigurationProperties(DataflowTaskLaunchRequestProperties.class)
public class DataFlowTaskLaunchRequestAutoConfiguration {
@Autowired
private BeanFactory beanFactory;
private EvaluationContext evaluationContext;
public final static String TASK_LAUNCH_REQUEST_FUNCTION_NAME = "taskLaunchRequest";
/**
* A {@link java.util.function.Function} to transform a {@link Message} payload to a {@link DataFlowTaskLaunchRequest}.
*
* @param taskLaunchRequestMessageProcessor a {@link TaskLaunchRequestMessageProcessor}.
*
* @return a {code DataFlowTaskLaunchRequest} Message.
*/
@Bean(name = TASK_LAUNCH_REQUEST_FUNCTION_NAME)
@ConditionalOnMissingBean(TaskLaunchRequestFunction.class)
public TaskLaunchRequestFunction taskLaunchRequest(TaskLaunchRequestMessageProcessor taskLaunchRequestMessageProcessor) {
return message -> taskLaunchRequestMessageProcessor.postProcessMessage(message);
}
@Bean
@ConditionalOnMissingBean(TaskNameMessageMapper.class)
public TaskNameMessageMapper taskNameMessageMapper(DataflowTaskLaunchRequestProperties taskLaunchRequestProperties) {
if (StringUtils.hasText(taskLaunchRequestProperties.getTaskNameExpression())) {
SpelExpressionParser expressionParser = new SpelExpressionParser();
Expression taskNameExpression = expressionParser.parseExpression(taskLaunchRequestProperties.getTaskNameExpression());
return new ExpressionEvaluatingTaskNameMessageMapper(taskNameExpression, this.evaluationContext);
}
return message -> taskLaunchRequestProperties.getTaskName();
}
@Bean
@ConditionalOnMissingBean(CommandLineArgumentsMessageMapper.class)
public CommandLineArgumentsMessageMapper commandLineArgumentsMessageMapper(
DataflowTaskLaunchRequestProperties dataflowTaskLaunchRequestProperties){
return new ExpressionEvaluatingCommandLineArgsMapper(dataflowTaskLaunchRequestProperties.getArgExpressions(),
this.evaluationContext);
}
@Bean
public TaskLaunchRequestSupplier taskLaunchRequestInitializer(
DataflowTaskLaunchRequestProperties taskLaunchRequestProperties){
return new DataflowTaskLaunchRequestPropertiesInitializer(taskLaunchRequestProperties);
}
@Bean
public TaskLaunchRequestMessageProcessor taskLaunchRequestMessageProcessor(
TaskLaunchRequestSupplier taskLaunchRequestInitializer,
TaskNameMessageMapper taskNameMessageMapper,
CommandLineArgumentsMessageMapper commandLineArgumentsMessageMapper) {
return new TaskLaunchRequestMessageProcessor(taskLaunchRequestInitializer,
taskNameMessageMapper,
commandLineArgumentsMessageMapper);
}
static class DataflowTaskLaunchRequestPropertiesInitializer extends TaskLaunchRequestSupplier {
DataflowTaskLaunchRequestPropertiesInitializer(
DataflowTaskLaunchRequestProperties taskLaunchRequestProperties){
this.commandLineArgumentSupplier(
() -> new ArrayList<>(taskLaunchRequestProperties.getArgs())
);
this.deploymentPropertiesSupplier(
() -> KeyValueListParser.parseCommaDelimitedKeyValuePairs(
taskLaunchRequestProperties.getDeploymentProperties())
);
this.taskNameSupplier(()->taskLaunchRequestProperties.getTaskName());
}
}
static class ExpressionEvaluatingCommandLineArgsMapper implements CommandLineArgumentsMessageMapper {
private final Map<String,Expression> argExpressionsMap;
private final EvaluationContext evaluationContext;
ExpressionEvaluatingCommandLineArgsMapper(String argExpressions, EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
this.argExpressionsMap = new HashMap<>();
if (StringUtils.hasText(argExpressions)) {
SpelExpressionParser expressionParser = new SpelExpressionParser();
KeyValueListParser.parseCommaDelimitedKeyValuePairs(argExpressions).forEach(
(k,v)-> argExpressionsMap.put(k, expressionParser.parseExpression(v)));
}
}
@Override
public Collection<String> processMessage(Message<?> message) {
return evaluateArgExpressions(message);
}
private Collection<String> evaluateArgExpressions(Message<?> message) {
List<String> results = new LinkedList<>();
this.argExpressionsMap.forEach((k, expression) ->
results.add(String.format("%s=%s", k, expression.getValue(this.evaluationContext, message))));
return results;
}
}
@PostConstruct
public void createEvaluationContext(){
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2018-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.stream.app.tasklaunchrequest;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
/**
* Base Properties to create a {@link DataFlowTaskLaunchRequest}.
*
* @author Chris Schaefer
* @author David Turanski
*/
@Validated
@ConfigurationProperties("task.launch.request")
public class DataflowTaskLaunchRequestProperties {
/**
* Comma separated list of optional args in key=value format.
*/
private List<String> args = new ArrayList<>();
/**
* Comma separated list of option args as SpEL expressions in key=value format.
*/
private String argExpressions = "";
/**
* Comma delimited list of deployment properties to be applied to the
* TaskLaunchRequest.
*/
private String deploymentProperties = "";
/**
* The Data Flow task name.
*/
private String taskName;
/**
* A SpEL expression to extract the task name from each Message, using the Message as the evaluation context.
*/
private String taskNameExpression;
@NotNull
public List<String> getArgs() {
return this.args;
}
public void setArgs(List<String> args) {
this.args = new ArrayList<>(args);
}
@NotNull
public String getDeploymentProperties() {
return this.deploymentProperties;
}
public void setDeploymentProperties(String deploymentProperties) {
this.deploymentProperties = deploymentProperties;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskNameExpression() {
return taskNameExpression;
}
public void setTaskNameExpression(String taskNameExpression) {
this.taskNameExpression = taskNameExpression;
}
public String getArgExpressions() {
return argExpressions;
}
public void setArgExpressions(String argExpressions) {
this.argExpressions = argExpressions;
}
@AssertFalse(message = "Cannot specify both 'taskName' and 'taskNameExpression'.")
public boolean isTaskNameAndTaskNameExpressionSet() {
return StringUtils.hasText(this.taskName) && StringUtils.hasText(this.taskNameExpression);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 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.stream.app.tasklaunchrequest;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.TaskNameMessageMapper;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
public class ExpressionEvaluatingTaskNameMessageMapper implements TaskNameMessageMapper {
private final Expression expression;
private final EvaluationContext evaluationContext;
public ExpressionEvaluatingTaskNameMessageMapper(Expression expression, EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
this.expression = expression;
}
@Override
public String processMessage(Message<?> message) {
return expression.getValue(evaluationContext, message).toString();
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 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.stream.app.tasklaunchrequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.StringUtils;
/**
* Parses a comma delimited list of key value pairs in which the values can contain commas as well.
*
* @author Chris Schaeffer
* @author David Turanski
**/
abstract class KeyValueListParser {
static Map<String, String> parseCommaDelimitedKeyValuePairs(String value) {
Map<String, String> keyValuePairs = new HashMap<>();
if (StringUtils.isEmpty(value)) {
return keyValuePairs;
}
ArrayList<String> pairs = new ArrayList<>();
String[] candidates = StringUtils.commaDelimitedListToStringArray(value);
for (int i = 0; i < candidates.length; i++) {
if (i > 0 && !candidates[i].contains("=")) {
pairs.add(pairs.get(pairs.size() - 1) + "," + candidates[i]);
}
else {
pairs.add(candidates[i]);
}
}
for (String pair : pairs) {
addKeyValuePair(pair, keyValuePairs);
}
return keyValuePairs;
}
private static void addKeyValuePair(String pair, Map<String, String> properties) {
int firstEquals = pair.indexOf('=');
if (firstEquals != -1) {
properties.put(pair.substring(0, firstEquals).trim(), pair.substring(firstEquals + 1).trim());
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 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.stream.app.tasklaunchrequest;
import java.util.function.Function;
import org.springframework.messaging.Message;
/**
* A marker interface useful for unambiguous dependency injection of this Function.
*
* @author David Turanski
**/
@FunctionalInterface
public interface TaskLaunchRequestFunction extends Function<Message<?>, Message<DataFlowTaskLaunchRequest>> {
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 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.stream.app.tasklaunchrequest;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.CommandLineArgumentsMessageMapper;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.TaskLaunchRequestSupplier;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.TaskNameMessageMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.StringUtils;
public class TaskLaunchRequestMessageProcessor implements MessagePostProcessor {
private final TaskNameMessageMapper taskNameMessageMapper;
private final CommandLineArgumentsMessageMapper commandLineArgumentsMessageMapper;
private final TaskLaunchRequestSupplier taskLaunchRequestInitializer;
public TaskLaunchRequestMessageProcessor(TaskLaunchRequestSupplier taskLaunchRequestInitializer,
TaskNameMessageMapper taskNameMessageMapper,
CommandLineArgumentsMessageMapper commandLIneArgumentsMessageMapper) {
this.taskLaunchRequestInitializer = taskLaunchRequestInitializer;
this.taskNameMessageMapper = taskNameMessageMapper;
this.commandLineArgumentsMessageMapper = commandLIneArgumentsMessageMapper;
}
@Override
public Message<DataFlowTaskLaunchRequest> postProcessMessage(Message<?> message) {
DataFlowTaskLaunchRequest taskLaunchRequest = taskLaunchRequestInitializer.get();
if (!StringUtils.hasText(taskLaunchRequest.getTaskName())) {
taskLaunchRequest.setTaskName(taskNameMessageMapper.processMessage(message));
Assert.hasText(taskLaunchRequest.getTaskName(), ()->
"'taskName' is required in " + DataFlowTaskLaunchRequest.class.getName());
}
taskLaunchRequest.addCommmandLineArguments(commandLineArgumentsMessageMapper.processMessage(message));
MessageBuilder<DataFlowTaskLaunchRequest> builder
= MessageBuilder.withPayload(taskLaunchRequest).copyHeaders(message.getHeaders());
return adjustHeaders(builder).build();
}
private MessageBuilder<DataFlowTaskLaunchRequest> adjustHeaders(MessageBuilder<DataFlowTaskLaunchRequest> builder) {
builder.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
return builder;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 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.stream.app.tasklaunchrequest.support;
import java.util.Collection;
import org.springframework.integration.handler.MessageProcessor;
public interface CommandLineArgumentsMessageMapper extends MessageProcessor<Collection<String>> {
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 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.stream.app.tasklaunchrequest.support;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.springframework.cloud.stream.app.tasklaunchrequest.DataFlowTaskLaunchRequest;
import org.springframework.util.Assert;
public class TaskLaunchRequestSupplier implements Supplier<DataFlowTaskLaunchRequest> {
private Supplier<String> taskNameSupplier;
private Supplier<List<String>> commandLineArgumentsSupplier;
private Supplier<Map<String, String>> deploymentPropertiesSupplier;
public TaskLaunchRequestSupplier taskNameSupplier(Supplier<String> taskNameSupplier) {
this.taskNameSupplier = taskNameSupplier;
return this;
}
public TaskLaunchRequestSupplier commandLineArgumentSupplier(Supplier<List<String>> commandLineArgumentsSupplier) {
this.commandLineArgumentsSupplier = commandLineArgumentsSupplier;
return this;
}
public TaskLaunchRequestSupplier deploymentPropertiesSupplier(Supplier<Map<String, String>> deploymentPropertiesSupplier) {
this.deploymentPropertiesSupplier = deploymentPropertiesSupplier;
return this;
}
@Override
public DataFlowTaskLaunchRequest get() {
Assert.notNull(this.taskNameSupplier, "'taskNameSupplier' is required.");
DataFlowTaskLaunchRequest dataFlowTaskLaunchRequest = new DataFlowTaskLaunchRequest();
dataFlowTaskLaunchRequest.setTaskName(this.taskNameSupplier.get());
if (this.commandLineArgumentsSupplier != null) {
dataFlowTaskLaunchRequest.setCommandlineArguments(this.commandLineArgumentsSupplier.get());
}
if (this.deploymentPropertiesSupplier != null) {
dataFlowTaskLaunchRequest.setDeploymentProperties(this.deploymentPropertiesSupplier.get());
}
return dataFlowTaskLaunchRequest;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 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.stream.app.tasklaunchrequest.support;
import org.springframework.integration.handler.MessageProcessor;
@FunctionalInterface
public interface TaskNameMessageMapper extends MessageProcessor<String> {
}

View File

@@ -0,0 +1,2 @@
configuration-properties.classes=\
org.springframework.cloud.stream.app.tasklaunchrequest.DataflowTaskLaunchRequestProperties

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.stream.app.tasklaunchrequest.DataFlowTaskLaunchRequestAutoConfiguration

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2018 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.stream.app.tasklaunchrequest;
import java.util.Map;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Chris Schaefer
* @author David Turanski
*/
public class KeyValueListParserTests {
@Test
public void testParseSimpleDeploymentProperty() {
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 1);
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.param"));
assertEquals("Invalid deployment value", "value", deploymentProperties.get("app.sftp.param"));
}
@Test
public void testParseSimpleDeploymentPropertyMultipleValues() {
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value1,value2,value3");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 1);
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.param"));
assertEquals("Invalid deployment value", "value1,value2,value3", deploymentProperties.get("app.sftp.param"));
}
@Test
public void testParseSpelExpressionMultipleValues() {
Map<String, String> argExpressions = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"arg1=payload.substr(0,2),arg2=headers['foo'],arg3=headers['bar']==false");
assertTrue("Invalid number of deployment properties: " + argExpressions.size(),
argExpressions.size() == 3);
assertTrue("Expected deployment key not found", argExpressions.containsKey("arg1"));
assertEquals("Invalid deployment value", "payload.substr(0,2)", argExpressions.get("arg1"));
assertTrue("Expected deployment key not found", argExpressions.containsKey("arg2"));
assertEquals("Invalid deployment value", "headers['foo']", argExpressions.get("arg2"));
assertTrue("Expected deployment key not found", argExpressions.containsKey("arg3"));
assertEquals("Invalid deployment value", "headers['bar']==false", argExpressions.get("arg3"));
}
@Test
public void testParseMultipleDeploymentPropertiesSingleValue() {
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value1,app.sftp.other.param=value2");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 2);
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.param"));
assertEquals("Invalid deployment value", "value1", deploymentProperties.get("app.sftp.param"));
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.other.param"));
assertEquals("Invalid deployment value", "value2", deploymentProperties.get("app.sftp.other.param"));
}
@Test
public void testParseMultipleDeploymentPropertiesMultipleValues() {
DataflowTaskLaunchRequestProperties taskLaunchRequestProperties = new DataflowTaskLaunchRequestProperties();
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value1,value2,app.sftp.other.param=other1,other2");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 2);
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.param"));
assertEquals("Invalid deployment value", "value1,value2", deploymentProperties.get("app.sftp.param"));
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.other.param"));
assertEquals("Invalid deployment value", "other1,other2", deploymentProperties.get("app.sftp.other.param"));
}
}

View File

@@ -0,0 +1,313 @@
/*
* Copyright 2018 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.stream.app.tasklaunchrequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import java.io.IOException;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.CommandLineArgumentsMessageMapper;
import org.springframework.cloud.stream.app.tasklaunchrequest.support.TaskNameMessageMapper;
import org.springframework.cloud.stream.binder.test.OutputDestination;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.test.binder.MessageCollectorAutoConfiguration;
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author David Turanski
**/
public class TaskLaunchRequestIntegrationTests {
private ApplicationContextRunner applicationContextRunner;
@Before
public void setUp() {
applicationContextRunner =
new ApplicationContextRunner().withUserConfiguration(TestChannelBinderConfiguration.class, TestApp.class);
}
@Test
public void noTaskLaunchRequestPropertiesAreRequired() {
applicationContextRunner.withPropertyValues("spring.jmx.enabled=false")
.run(context -> {
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
Message<byte[]> message =
MessageBuilder.withPayload("hello".getBytes()).build();
input.send(message);
Message<byte[]> response = target.receive(1000);
assertThat(response.getPayload()).isEqualTo(message.getPayload());
});
}
@Test
public void simpleDataflowTaskLaunchRequest() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false",
"spring.cloud.stream.function.definition=taskLaunchRequest",
"task.launch.request.task-name=foo")
.run(context -> {
DataFlowTaskLaunchRequest dataFlowTaskLaunchRequest = verifyAndreceiveDataFlowTaskLaunchRequest(context);
assertThat(dataFlowTaskLaunchRequest.getTaskName()).isEqualTo("foo");
assertThat(dataFlowTaskLaunchRequest.getCommandlineArguments()).hasSize(0);
assertThat(dataFlowTaskLaunchRequest.getDeploymentProperties()).hasSize(0);
});
}
@Test
public void dataflowTaskLaunchRequestWithArgsAndDeploymentProperties() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false", "spring.cloud.stream.function.definition=taskLaunchRequest",
"task.launch.request.task-name=foo", "task.launch.request.args=foo=bar,baz=boo",
"task.launch.request.deploymentProperties=count=3")
.run(context -> {
DataFlowTaskLaunchRequest dataFlowTaskLaunchRequest = verifyAndreceiveDataFlowTaskLaunchRequest(context);
assertThat(dataFlowTaskLaunchRequest.getTaskName()).isEqualTo("foo");
assertThat(dataFlowTaskLaunchRequest.getCommandlineArguments()).containsExactlyInAnyOrder("foo=bar",
"baz=boo");
assertThat(dataFlowTaskLaunchRequest.getDeploymentProperties()).containsOnly(entry("count", "3"));
});
}
@Test
public void dataflowTaskLaunchRequestWithCommandLineArgsMessageMapper() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false", "spring.cloud.stream.function.definition=taskLaunchRequest",
"task.launch.request.task-name=foo", "enhanceTLRArgs=true")
.run(context -> {
DataFlowTaskLaunchRequest dataFlowTaskLaunchRequest = verifyAndreceiveDataFlowTaskLaunchRequest(context);
assertThat(dataFlowTaskLaunchRequest.getTaskName()).isEqualTo("foo");
assertThat(dataFlowTaskLaunchRequest.getCommandlineArguments()).hasSize(1);
assertThat(dataFlowTaskLaunchRequest.getCommandlineArguments()).containsExactly("runtimeArg");
});
}
@Test
public void taskLaunchRequestWithArgExpressions() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false",
"spring.cloud.stream.function.definition=taskLaunchRequest",
"task.launch.request.task-name=foo",
"task.launch.request.arg-expressions=foo=payload.toUpperCase(),bar=payload.substring(0,2)")
.run(context -> {
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
Message<String> message = MessageBuilder.withPayload("hello").build();
input.send(message);
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
Message<byte[]> response = target.receive(1000);
assertThat(response).isNotNull();
DataFlowTaskLaunchRequest request = objectMapper.readValue(response.getPayload(),
DataFlowTaskLaunchRequest.class);
assertThat(request.getCommandlineArguments()).containsExactlyInAnyOrder("foo=HELLO", "bar=he");
});
}
@Test
public void taskLaunchRequestWithIntPayload() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false", "spring.cloud.stream.function.definition=taskLaunchRequest",
"task.launch.request.task-name=foo",
"task.launch.request.arg-expressions=i=payload")
.run(context -> {
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
Message<Integer> message =
MessageBuilder.withPayload(123).build();
input.send(message);
Message<byte[]> response = target.receive(1000);
assertThat(response).isNotNull();
DataFlowTaskLaunchRequest request = objectMapper.readValue(response.getPayload(),
DataFlowTaskLaunchRequest.class);
assertThat(request.getCommandlineArguments()).containsExactly("i=123");
});
}
@Test
public void taskNameExpression() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false", "spring.cloud.stream.function.definition=taskLaunchRequest",
"task.launch.request.task-name-expression=payload+'_task'")
.run(context -> {
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
Message<String> message = MessageBuilder.withPayload("foo").build();
input.send(message);
Message<byte[]> response = target.receive(1000);
assertThat(response).isNotNull();
DataFlowTaskLaunchRequest request = objectMapper.readValue(response.getPayload(),
DataFlowTaskLaunchRequest.class);
assertThat(request.getTaskName()).isEqualTo("foo_task");
});
}
@Test
public void customTaskNameExtractor() {
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false", "spring.cloud.stream.function.definition=taskLaunchRequest",
"customTaskNameExtractor=true")
.run(context -> {
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
Message<String> message = MessageBuilder.withPayload("foo").build();
input.send(message);
Message<byte[]> response = target.receive(1000);
assertThat(response).isNotNull();
DataFlowTaskLaunchRequest request = objectMapper.readValue(response.getPayload(),
DataFlowTaskLaunchRequest.class);
assertThat(request.getTaskName()).isEqualTo("fooTask");
});
//TODO: Workaround for https://github.com/spring-cloud/spring-cloud-stream/issues/1876
applicationContextRunner.withPropertyValues(
"spring.jmx.enabled=false", "spring.cloud.stream.function.definition=taskLaunchRequest",
"customTaskNameExtractor=true")
.run(context -> {
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
Message<String> message = MessageBuilder.withPayload("bar").build();
input.send(message);
Message<byte[]> response = target.receive(1000);
assertThat(response).isNotNull();
DataFlowTaskLaunchRequest request = objectMapper.readValue(response.getPayload(),
DataFlowTaskLaunchRequest.class);
assertThat(request.getTaskName()).isEqualTo("defaultTask");
});
}
private DataFlowTaskLaunchRequest verifyAndreceiveDataFlowTaskLaunchRequest(ApplicationContext context)
throws IOException {
MessageChannel input = context.getBean("input", MessageChannel.class);
OutputDestination target = context.getBean(OutputDestination.class);
MessageBuilder<byte[]> builder = MessageBuilder.withPayload(new byte[] {});
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
input.send(builder.build());
Message<byte[]> message = target.receive(1000);
assertThat(message).isNotNull();
return objectMapper.readValue(message.getPayload(),
DataFlowTaskLaunchRequest.class);
}
@EnableAutoConfiguration(exclude = { TestSupportBinderAutoConfiguration.class,
MessageCollectorAutoConfiguration.class })
@EnableBinding(Processor.class)
static class TestApp {
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
@ConditionalOnProperty("customTaskNameExtractor")
TaskNameMessageMapper taskNameExtractor() {
return message -> ((String)(message.getPayload())).equalsIgnoreCase("foo") ?
"fooTask" :
"defaultTask";
}
@Bean
@ConditionalOnProperty("enhanceTLRArgs")
CommandLineArgumentsMessageMapper commandLineArgumentsProvider(){
return message -> Collections.singletonList("runtimeArg");
}
@Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(Processor.INPUT)
.channel(Processor.OUTPUT).get();
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2018-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.stream.app.tasklaunchrequest;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.integration.config.EnableIntegration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author David Turanski
**/
public class TaskLaunchRequestPropertiesTests {
@Test
public void deploymentPropertiesCanBeCustomized() {
DataflowTaskLaunchRequestProperties properties = getBatchProperties(
"task.launch.request.deploymentProperties:prop1=val1,prop2=val2");
assertThat(properties.getDeploymentProperties()).isEqualTo("prop1=val1,prop2=val2");
}
@Test
public void parametersCanBeCustomized() {
DataflowTaskLaunchRequestProperties properties = getBatchProperties(
"task.launch.request.args:jp1=jpv1,jp2=jpv2");
List<String> args = properties.getArgs();
assertThat(args).isNotNull();
assertThat(args).hasSize(2);
assertThat(args.get(0)).isEqualTo("jp1=jpv1");
assertThat(args.get(1)).isEqualTo("jp2=jpv2");
}
private DataflowTaskLaunchRequestProperties getBatchProperties(String... var) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (var != null) {
TestPropertyValues.of(var).applyTo(context);
}
context.register(Conf.class);
context.refresh();
return context.getBean(DataflowTaskLaunchRequestProperties.class);
}
@Configuration
@EnableIntegration
@EnableConfigurationProperties(DataflowTaskLaunchRequestProperties.class)
@Import(DataFlowTaskLaunchRequestAutoConfiguration.class)
static class Conf {
}
}

View File

@@ -0,0 +1,29 @@
<?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">
<parent>
<artifactId>stream-apps-common</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-test-support</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.test;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.PropertiesPropertySource;
/**
* Unlike the {@code PropertiesInitializer}, this does not require boot infrastructure
* to add properties to the context. Used for testing generated apps where the
* {@code ApplicationContextInitializer} can't be used. Since it's a BDRPP, it runs
* before any BFPPs - i.e. as early as possible.
*
* @author Gary Russell
*
*/
public class BinderTestPropertiesInitializer implements BeanDefinitionRegistryPostProcessor {
private final ConfigurableApplicationContext context;
private final Properties properties;
public BinderTestPropertiesInitializer(ConfigurableApplicationContext context, Properties properties) {
this.context = context;
this.properties = properties;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.context.getEnvironment().getPropertySources()
.addLast(new PropertiesPropertySource("scsAppProperties", properties));
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2014-2016 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.test;
import java.util.Properties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.PropertiesPropertySource;
/**
* @author David Turanski
*/
public class PropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public static Properties PROPERTIES;
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
configurableApplicationContext.getEnvironment().getPropertySources().addLast(new
PropertiesPropertySource("applicationOptions", PROPERTIES));
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.test.file.remote;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.rules.TemporaryFolder;
/**
* Abstract base class for tests requiring remote file servers, e.g. (S)FTP.
*
* @author Gary Russell
*
*/
public abstract class RemoteFileTestSupport {
protected static final int port = 0;
@ClassRule
public static final TemporaryFolder remoteTemporaryFolder = new TemporaryFolder();
@ClassRule
public static final TemporaryFolder localTemporaryFolder = new TemporaryFolder();
protected volatile File sourceRemoteDirectory;
protected volatile File targetRemoteDirectory;
protected volatile File sourceLocalDirectory;
protected volatile File targetLocalDirectory;
public File getSourceRemoteDirectory() {
return sourceRemoteDirectory;
}
public File getTargetRemoteDirectory() {
return targetRemoteDirectory;
}
public File getSourceLocalDirectory() {
return sourceLocalDirectory;
}
public File getTargetLocalDirectory() {
return targetLocalDirectory;
}
/**
* Default implementation creates the following folder structures:
*
* <pre class="code">
* $ tree remoteSource/
* remoteSource/
* ├── remoteSource1.txt - contains 'source1'
* ├── remoteSource2.txt - contains 'source2'
* remoteTarget/
* $ tree localSource/
* localSource/
* ├── localSource1.txt - contains 'local1'
* ├── localSource2.txt - contains 'local2'
* localTarget/
* </pre>
*
* The intent is tests retrieve from remoteSource and verify arrival in localTarget or send from localSource and verify
* arrival in remoteTarget.
* <p>
* Subclasses can change 'remote' in these names by overriding {@link #prefix()} or override this method completely to
* create a different structure.
* <p>
* While a single server exists for all tests, the directory structure is rebuilt for each test.
* @throws IOException IO Exception.
*/
@Before
public void setupFolders() throws IOException {
String prefix = prefix();
recursiveDelete(new File(remoteTemporaryFolder.getRoot(), prefix + "Source"));
this.sourceRemoteDirectory = remoteTemporaryFolder.newFolder(prefix + "Source");
recursiveDelete(new File(remoteTemporaryFolder.getRoot(), prefix + "Target"));
this.targetRemoteDirectory = remoteTemporaryFolder.newFolder(prefix + "Target");
recursiveDelete(new File(localTemporaryFolder.getRoot(), "localSource"));
this.sourceLocalDirectory = localTemporaryFolder.newFolder("localSource");
recursiveDelete(new File(localTemporaryFolder.getRoot(), "localTarget"));
this.targetLocalDirectory = localTemporaryFolder.newFolder("localTarget");
File file = new File(sourceRemoteDirectory, prefix + "Source1.txt");
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
fos.write("source1".getBytes());
fos.close();
file = new File(sourceRemoteDirectory, prefix + "Source2.txt");
file.createNewFile();
fos = new FileOutputStream(file);
fos.write("source2".getBytes());
fos.close();
file = new File(sourceLocalDirectory, "localSource1.txt");
file.createNewFile();
fos = new FileOutputStream(file);
fos.write("local1".getBytes());
fos.close();
file = new File(sourceLocalDirectory, "localSource2.txt");
file.createNewFile();
fos = new FileOutputStream(file);
fos.write("local2".getBytes());
fos.close();
}
public void recursiveDelete(File file) {
if (file != null && file.exists()) {
File[] files = file.listFiles();
if (files != null) {
for (File fyle : files) {
if (fyle.isDirectory()) {
recursiveDelete(fyle);
}
else {
fyle.delete();
}
}
}
file.delete();
}
}
/**
* Prefix for directory/file structure; default 'remote'.
* @return the prefix.
*/
protected String prefix() {
return "remote";
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.test.ip;
import java.util.Properties;
import org.springframework.cloud.stream.app.test.BinderTestPropertiesInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Generated app test configuration for the IP (TCP) sink.
*
* @author Gary Russell
*
*/
@Configuration
public class IpSinkTestConfiguration {
@Bean
public static BinderTestPropertiesInitializer loadProps(ConfigurableApplicationContext context) {
// minimal properties for the context to load
Properties properties = new Properties();
properties.put("host", "localhost");
return new BinderTestPropertiesInitializer(context, properties);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.test.ip;
import java.util.Properties;
import org.springframework.cloud.stream.app.test.BinderTestPropertiesInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Generated app test configuration for IP (TCP, UDP) sources.
*
* @author Gary Russell
*
*/
@Configuration
public class IpSourceTestConfiguration {
@Bean
public static BinderTestPropertiesInitializer loadProps(ConfigurableApplicationContext context) {
// minimal properties for the context to load
Properties properties = new Properties();
properties.put("port", 0);
return new BinderTestPropertiesInitializer(context, properties);
}
}

View File

@@ -0,0 +1,43 @@
///*
// * Copyright 2016 the original author or authors.
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * https://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
//package org.springframework.cloud.stream.app.test.redis;
//
//import org.springframework.cloud.stream.test.junit.AbstractExternalResourceTestSupport;
//import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
//
///**
// * Porting from https://github.com/spring-cloud/spring-cloud-stream/blob/1.0.x/spring-cloud-stream-test-support-internal
// *
// * @author Soby Chacko
// */
//public class RedisTestSupport extends AbstractExternalResourceTestSupport<LettuceConnectionFactory> {
//
// public RedisTestSupport() {
// super("REDIS");
// }
// @Override
// protected void cleanupResource() throws Exception {
// resource.destroy();
// }
//
// @Override
// protected void obtainResource() throws Exception {
// resource = new LettuceConnectionFactory();
// resource.afterPropertiesSet();
// resource.getConnection().close();
// }
//}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.app.test.script;
import java.util.Properties;
import org.springframework.cloud.stream.app.test.BinderTestPropertiesInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Test configuration for generated scriptable apps.
*
* @author Gary Russell
*
*/
@Configuration
public class ScriptableTestConfiguration {
@Bean
public BinderTestPropertiesInitializer loadProps(ConfigurableApplicationContext context) {
// minimal properties for the context to load
Properties properties = new Properties();
properties.put("script", "foo");
properties.put("language", "ruby");
return new BinderTestPropertiesInitializer(context, properties);
}
}

View File

@@ -0,0 +1,348 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>stream-apps-parent</artifactId>
<version>3.0.0.BUILD-SNAPSHOT</version>
<name>stream-apps-parent</name>
<description>Infrastructure for stream apps</description>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.M4</version>
</parent>
<properties>
<java.version>1.8</java.version>
<stream-apps-core.version>3.0.0.M2</stream-apps-core.version>
<spring-boot.version>2.3.0.M4</spring-boot.version>
<app-metadata-maven-plugin-version>2.0.1.M1</app-metadata-maven-plugin-version>
<scst-app-maven-plugin.version>2.0.0.M2</scst-app-maven-plugin.version>
<stream-apps-docs-plugin.version>3.0.0.M1</stream-apps-docs-plugin.version>
<spring-cloud-stream.version>3.0.2.RELEASE</spring-cloud-stream.version>
<spring-cloud-function-dependencies.version>3.0.2.RELEASE</spring-cloud-function-dependencies.version>
<spring-cloud-dependencies.version>Hoxton.SR2</spring-cloud-dependencies.version>
<spring-cloud-stream-dependencies.version>Horsham.SR2</spring-cloud-stream-dependencies.version>
<java-cfenv-boot.version>2.1.1.RELEASE</java-cfenv-boot.version>
<java-functions.version>1.0.0.BUILD-SNAPSHOT</java-functions.version>
<prometheus-rsocket.version>0.9.0</prometheus-rsocket.version>
</properties>
<modules>
<module>common</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-dependencies</artifactId>
<version>${spring-cloud-stream-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<classifier>test-binder</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<comments>Copyright 2014-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
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.</comments>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/pivotal/java-functions.git</connection>
<developerConnection>scm:git:ssh://git@github.com/pivotal/java-functions.git</developerConnection>
<url>https://github.com/pivotal/java-functions</url>
</scm>
<distributionManagement>
<repository>
<id>repo.spring.io</id>
<name>Spring Release Repository</name>
<url>https://repo.spring.io/libs-release-local</url>
</repository>
<snapshotRepository>
<id>repo.spring.io</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
</snapshotRepository>
</distributionManagement>
<profiles>
<profile>
<id>milestone</id>
<distributionManagement>
<repository>
<id>repo.spring.io</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/libs-milestone-local</url>
</repository>
</distributionManagement>
</profile>
<profile>
<id>central</id>
<build>
<plugins>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>sonatype-nexus-staging</id>
<name>Nexus Release Repository</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
</profile>
</profiles>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-app-starter-doc-maven-plugin</artifactId>
<version>${stream-apps-docs-plugin.version}</version>
<executions>
<execution>
<id>generate-documentation</id>
<phase>verify</phase>
<goals>
<goal>generate-documentation</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.cloud.stream.app.plugin</groupId>
<artifactId>spring-cloud-stream-app-maven-plugin</artifactId>
<version>${scst-app-maven-plugin.version}</version>
<executions>
<execution>
<id>app-gen</id>
<phase>package</phase>
<goals>
<goal>generate-app</goal>
</goals>
</execution>
</executions>
<configuration>
<generatedProjectHome>${basedir}/apps</generatedProjectHome>
<javaVersion>1.8</javaVersion>
<bootVersion>${spring-boot.version}</bootVersion>
<binders>
<item>kafka</item>
<item>rabbit</item>
</binders>
<boms>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-dependencies</artifactId>
<version>${spring-cloud-stream-dependencies.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>${spring-cloud-function-dependencies.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
</dependency>
</boms>
<globalDependencies>
<dependency>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>stream-apps-security-common</artifactId>
<version>${stream-apps-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>stream-apps-micrometer-common</artifactId>
<version>${stream-apps-core.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer.prometheus</groupId>
<artifactId>prometheus-rsocket-spring</artifactId>
<version>${prometheus-rsocket.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-datadog</artifactId>
</dependency>
<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-boot</artifactId>
<version>${java-cfenv-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</globalDependencies>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-libs-release</id>
<name>Spring Libs Release</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-milestone-release</id>
<name>Spring Milestone Release</name>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
<pluginRepository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
</pluginRepository>
<pluginRepository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,44 @@
= 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/]

View File

@@ -0,0 +1,201 @@
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.

View File

@@ -0,0 +1,87 @@
<?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>
<artifactId>apps-metadata</artifactId>
<version>Fahrenheit.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.cloud.stream.app</groupId>
<artifactId>stream-apps-parent</artifactId>
<version>3.0.0.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<modules>
<module>stream-apps-docs</module>
<module>stream-apps-descriptor</module>
</modules>
<profiles>
<profile>
<id>spring</id>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</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>
<repository>
<id>spring-libs-release</id>
<name>Spring Libs Release</name>
<url>https://repo.spring.io/libs-release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-milestone-release</id>
<name>Spring Milestone Release</name>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,29 @@
#!/bin/bash
# The script takes two arguments - currently released version for tagging and next version
if [ "$#" -ne 2 ]; then
echo "Please specify the released version and the next version"
exit
fi
pushd /tmp
git clone git@github.com:spring-cloud-stream-app-starters/core.git
cd core
git tag v$1
git push origin v$1
./mvnw versions:set -DnewVersion=$2 -DgenerateBackupPoms=false
./mvnw versions:set -DnewVersion=$2 -DgenerateBackupPoms=false -pl :app-starters-core-dependencies
sed -i '' 's/<app-starters-core-dependencies.version>.*/<app-starters-core-dependencies.version>'"$2"'<\/app-starters-core-dependencies.version>/g' pom.xml
git commit -am"Next version - $2"
git push origin master
cd ..
rm -rf core
popd

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# The script takes one argument - release version
if [ "$#" -ne 1 ]; then
echo "Please specify the release version"
exit
fi
pushd /tmp
git clone git@github.com:spring-cloud-stream-app-starters/core.git
cd core
./mvnw versions:set -DnewVersion=$1 -DgenerateBackupPoms=false -U
./mvnw versions:set -DnewVersion=$1 -DgenerateBackupPoms=false -pl :app-starters-core-dependencies -U
sed -i '' 's/<app-starters-core-dependencies.version>.*/<app-starters-core-dependencies.version>'"$1"'<\/app-starters-core-dependencies.version>/g' pom.xml
snapshotlines=$(find . -type f -name pom.xml | xargs grep SNAPSHOT | wc -l)
rclines=$(find . -type f -name pom.xml | xargs grep .RC | wc -l)
milestonelines=$(find . -type f -name pom.xml | xargs grep version | grep .M | wc -l)
if [ $snapshotlines -eq 0 ] && [ $rclines -eq 0 ] && [$milestonelines -eq 0 ]; then
echo "All clear"
else
echo "Snapshots found."
find . -type f -name pom.xml | xargs grep SNAPSHOT
echo "SNAPSHOTS: " $snapshotlines
exit 1
fi
cd ..
rm -rf core
popd

View File

@@ -0,0 +1,39 @@
#!/bin/bash
# The script takes one argument - release version
if [ "$#" -ne 1 ]; then
echo "Please specify the release version"
exit
fi
pushd /tmp
git clone git@github.com:spring-cloud-stream-app-starters/core.git
cd core
./mvnw versions:set -DnewVersion=$1 -DgenerateBackupPoms=false -U
./mvnw versions:set -DnewVersion=$1 -DgenerateBackupPoms=false -pl :app-starters-core-dependencies -U
sed -i '' 's/<app-starters-core-dependencies.version>.*/<app-starters-core-dependencies.version>'"$1"'<\/app-starters-core-dependencies.version>/g' pom.xml
snapshotlines=$(find . -type f -name pom.xml | xargs grep SNAPSHOT | wc -l)
rclines=$(find . -type f -name pom.xml | xargs grep .RC | wc -l)
milestonelines=$(find . -type f -name pom.xml | xargs grep version | grep .M | wc -l)
if [ $snapshotlines -eq 0 ] && [ $rclines -eq 0 ] && [$milestonelines -eq 0 ]; then
echo "All clear"
git commit -am"Update version to $1"
git push origin master
else
echo "Snapshots found."
echo "SNAPSHOTS: " $snapshotlines
echo "Milestones: " $milestonelines
echo "RC: " $rclines
exit 1
fi
cd ..
rm -rf core
popd

View File

@@ -0,0 +1,140 @@
<?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">
<parent>
<artifactId>stream-apps-release-train</artifactId>
<groupId>org.springframework.cloud.stream.app</groupId>
<version>Fahrenheit.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stream-apps-descriptor</artifactId>
<name>stream-apps-descriptor</name>
<packaging>jar</packaging>
<properties>
<cassandra-sink.version>3.0.0.BUILD-SNAPSHOT</cassandra-sink.version>
<counter-sink.version>3.0.0.BUILD-SNAPSHOT</counter-sink.version>
<jdbc-sink.version>3.0.0.BUILD-SNAPSHOT</jdbc-sink.version>
<log-sink.version>3.0.0.BUILD-SNAPSHOT</log-sink.version>
<mongodb-sink.version>3.0.0.BUILD-SNAPSHOT</mongodb-sink.version>
<rabbit-sink.version>3.0.0.BUILD-SNAPSHOT</rabbit-sink.version>
<http-source.version>3.0.0.BUILD-SNAPSHOT</http-source.version>
<jdbc-source.version>3.0.0.BUILD-SNAPSHOT</jdbc-source.version>
<mongodb-source.version>3.0.0.BUILD-SNAPSHOT</mongodb-source.version>
<time-source.version>3.0.0.BUILD-SNAPSHOT</time-source.version>
<filter-processor.version>3.0.0.BUILD-SNAPSHOT</filter-processor.version>
<splitter-processor.version>3.0.0.BUILD-SNAPSHOT</splitter-processor.version>
<transform-processor.version>3.0.0.BUILD-SNAPSHOT</transform-processor.version>
</properties>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>META-INF/kafka-apps-maven.properties</include>
<include>META-INF/rabbit-apps-maven.properties</include>
<include>META-INF/kafka-apps-docker.properties</include>
<include>META-INF/rabbit-apps-docker.properties</include>
<include>META-INF/kafka-apps-maven-repo-url.properties</include>
<include>META-INF/rabbit-apps-maven-repo-url.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source><![CDATA[
pom.properties['repo-spring-io']=
"${jdbc-source.version}".contains('BUILD-SNAPSHOT') ? 'repo.spring.io/snapshot' :
"${jdbc-source.version}".contains('RELEASE') ? 'repo.spring.io/release' : 'repo.spring.io/milestone';
pom.properties['cassandra-sink-docker.tag']=
"${cassandra-sink.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${cassandra-sink.version}";
pom.properties['counter-sink-docker.tag']=
"${counter-sink.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${counter-sink.version}";
pom.properties['jdbc-sink-docker.tag']=
"${jdbc-sink.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${jdbc-sink.version}";
pom.properties['log-sink-docker.tag']=
"${log-sink.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${log-sink.version}";
pom.properties['mongodb-sink-docker.tag']=
"${mongodb-sink.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${mongodb-sink.version}";
pom.properties['rabbit-sink-docker.tag']=
"${rabbit-sink.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${rabbit-sink.version}";
pom.properties['http-source-docker.tag']=
"${http-source.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${http-source.version}";
pom.properties['jdbc-source-docker.tag']=
"${jdbc-source.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${jdbc-source.version}";
pom.properties['mongodb-source-docker.tag']=
"${mongodb-source.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${mongodb-source.version}";
pom.properties['time-source-docker.tag']=
"${time-source.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${time-source.version}";
pom.properties['filter-processor-docker.tag']=
"${filter-processor.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${filter-processor.version}";
pom.properties['splitter-processor-docker.tag']=
"${splitter-processor.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${splitter-processor.version}";
pom.properties['transform-processor-docker.tag']=
"${transform-processor.version}".contains('BUILD-SNAPSHOT') ? 'latest' : "${transform-processor.version}";
]]></source>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>attach-artifacts</id>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>target/classes/META-INF/kafka-apps-maven.properties</file>
<type>stream-apps-kafka-maven</type>
</artifact>
<artifact>
<file>target/classes/META-INF/kafka-apps-docker.properties</file>
<type>stream-apps-kafka-docker</type>
</artifact>
<artifact>
<file>target/classes/META-INF/rabbit-apps-maven.properties</file>
<type>stream-apps-rabbit-maven</type>
</artifact>
<artifact>
<file>target/classes/META-INF/rabbit-apps-docker.properties</file>
<type>stream-apps-rabbit-docker</type>
</artifact>
<artifact>
<file>target/classes/META-INF/kafka-apps-maven-repo-url.properties</file>
<type>kafka-apps-maven-repo-url.properties</type>
</artifact>
<artifact>
<file>target/classes/META-INF/rabbit-apps-maven-repo-url.properties</file>
<type>rabbit-apps-maven-repo-url.properties</type>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,13 @@
sink.cassandra=docker:springcloudstream/cassandra-sink-kafka:@cassandra-sink-docker.tag@
sink.counter=docker:springcloudstream/counter-sink-kafka:@counter-sink-docker.tag@
sink.jdbc=docker:springcloudstream/jdbc-sink-kafka:@jdbc-sink-docker.tag@
sink.log=docker:springcloudstream/log-sink-kafka:@log-sink-docker.tag@
sink.mongodb=docker:springcloudstream/mongodb-sink-kafka:@mongodb-sink-docker.tag@
sink.rabbit=docker:springcloudstream/rabbit-sink-kafka:@rabbit-sink-docker.tag@
source.http=docker:springcloudstream/http-source-kafka:@http-source-docker.tag@
source.jdbc=docker:springcloudstream/jdbc-source-kafka:@jdbc-source-docker.tag@
source.mongodb=docker:springcloudstream/mongodb-source-kafka:@mongodb-source-docker.tag@
source.time=docker:springcloudstream/time-source-kafka:@time-source-docker.tag@
processor.filter=docker:springcloudstream/filter-processor-kafka:@filter-processor-docker.tag@
processor.splitter=docker:springcloudstream/splitter-processor-kafka:@splitter-processor-docker.tag@
processor.transform=docker:springcloudstream/transform-processor-kafka:@transform-processor-docker.tag@

View File

@@ -0,0 +1,26 @@
sink.cassandra=https://@repo-spring-io@/org/springframework/cloud/stream/app/cassandra-sink-kafka/@cassandra-sink.version@/cassandra-sink-kafka-@cassandra-sink.version@.jar
sink.cassandra.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/cassandra-sink-kafka/@cassandra-sink.version@/cassandra-sink-kafka-@cassandra-sink.version@-metadata.jar
sink.counter=https://@repo-spring-io@/org/springframework/cloud/stream/app/counter-sink-kafka/@counter-sink.version@/counter-sink-kafka-@counter-sink.version@.jar
sink.counter.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/counter-sink-kafka/@counter-sink.version@/counter-sink-kafka-@counter-sink.version@-metadata.jar
sink.jdbc=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-sink-kafka/@jdbc-sink.version@/jdbc-sink-kafka-@jdbc-sink.version@.jar
sink.jdbc.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-sink-kafka/@jdbc-sink.version@/jdbc-sink-kafka-@jdbc-sink.version@-metadata.jar
sink.log=https://@repo-spring-io@/org/springframework/cloud/stream/app/log-sink-kafka/@log-sink.version@/log-sink-kafka-@log-sink.version@.jar
sink.log.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/log-sink-kafka/@log-sink.version@/log-sink-kafka-@log-sink.version@-metadata.jar
sink.mongodb=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-sink-kafka/@mongodb-sink.version@/mongodb-sink-kafka-@mongodb-sink.version@.jar
sink.mongodb.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-sink-kafka/@mongodb-sink.version@/mongodb-sink-kafka-@mongodb-sink.version@-metadata.jar
sink.rabbit=https://@repo-spring-io@/org/springframework/cloud/stream/app/rabbit-sink-kafka/@rabbit-sink.version@/rabbit-sink-kafka-@rabbit-sink.version@.jar
sink.rabbit.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/rabbit-sink-kafka/@rabbit-sink.version@/rabbit-sink-kafka-@rabbit-sink.version@-metadata.jar
source.http=https://@repo-spring-io@/org/springframework/cloud/stream/app/http-source-kafka/@http-source.version@/http-source-kafka-@http-source.version@.jar
source.http.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/http-source-kafka/@http-source.version@/http-source-kafka-@http-source.version@-metadata.jar
source.jdbc=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-source-kafka/@jdb-source.version@/jdbc-source-kafka-@jdbc-source.version@.jar
source.jdbc.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-source-kafka/@jdb-source.version@/jdbc-source-kafka-@jdbc-source.version@-metadata.jar
source.mongodb=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-source-kafka/@mongodb-source.version@/mongodb-source-kafka-@mongodb-source.version@.jar
source.mongodb.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-source-kafka/@mongodb-source.version@/mongodb-source-kafka-@mongodb-source.version@-metadata.jar
source.time=https://@repo-spring-io@/org/springframework/cloud/stream/app/time-source-kafka/@time-source.version@/time-source-kafka-@time-source.version@.jar
source.time.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/time-source-kafka/@time-source.version@/time-source-kafka-@time-source.version@-metadata.jar
processor.filter=https://@repo-spring-io@/org/springframework/cloud/stream/app/filter-processor-kafka/@filter-processor.version@/filter-processor-kafka-@filter-processor.version@.jar
processor.filter.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/filter-processor-kafka/@filter-processor.version@/filter-processor-kafka-@filter-processor.version@-metadata.jar
processor.splitter=https://@repo-spring-io@/org/springframework/cloud/stream/app/splitter-processor-kafka/@splitter-processor.version@/splitter-processor-kafka-@splitter-processor.version@.jar
processor.splitter.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/splitter-processor-kafka/@splitter-processor.version@/splitter-processor-kafka-@splitter-processor.version@-metadata.jar
processor.transform=https://@repo-spring-io@/org/springframework/cloud/stream/app/transform-processor-kafka/@transform-processor.version@/transform-processor-kafka-@transform-processor.version@.jar
processor.transform.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/transform-processor-kafka/@transform-processor.version@/transform-processor-kafka-@transform-processor.version@-metadata.jar

View File

@@ -0,0 +1,27 @@
sink.cassandra=maven://org.springframework.cloud.stream.app:cassandra-sink-kafka:@cassandra-sink.version@
sink.cassandra.metadata=maven://org.springframework.cloud.stream.app:cassandra-sink-kafka:jar:metadata:@cassandra-sink.version@
sink.counter=maven://org.springframework.cloud.stream.app:counter-sink-kafka:@counter-sink.version@
sink.counter.metadata=maven://org.springframework.cloud.stream.app:counter-sink-kafka:jar:metadata:@counter-sink.version@
sink.jdbc=maven://org.springframework.cloud.stream.app:jdbc-sink-kafka:@jdbc-sink.version@
sink.jdbc.metadata=maven://org.springframework.cloud.stream.app:jdbc-sink-kafka:jar:metadata:@jdbc-sink.version@
sink.log=maven://org.springframework.cloud.stream.app:log-sink-kafka:@log-sink.version@
sink.log.metadata=maven://org.springframework.cloud.stream.app:log-sink-kafka:jar:metadata:@log-sink.version@
sink.mongodb=maven://org.springframework.cloud.stream.app:mongodb-sink-kafka:@mongodb-sink.version@
sink.mongodb.metadata=maven://org.springframework.cloud.stream.app:mongodb-sink-kafka:jar:metadata:@mongodb-sink.version@
sink.rabbit=maven://org.springframework.cloud.stream.app:rabbit-sink-kafka:@rabbit-sink.version@
sink.rabbit.metadata=maven://org.springframework.cloud.stream.app:rabbit-sink-kafka:jar:metadata:@rabbit-sink.version@
source.http=maven://org.springframework.cloud.stream.app:http-source-kafka:@http-source.version@
source.http.metadata=maven://org.springframework.cloud.stream.app:http-source-kafka:jar:metadata:@http-source.version@
source.jdbc=maven://org.springframework.cloud.stream.app:jdbc-source-kafka:@jdbc-source.version@
source.jdbc.metadata=maven://org.springframework.cloud.stream.app:jdbc-source-kafka:jar:metadata:@jdbc-source.version@
source.mongodb=maven://org.springframework.cloud.stream.app:mongodb-source-kafka:@mongodb-source.version@
source.mongodb.metadata=maven://org.springframework.cloud.stream.app:mongodb-source-kafka:jar:metadata:@mongodb-source.version@
source.time=maven://org.springframework.cloud.stream.app:time-source-kafka:@time-source.version@
source.time.metadata=maven://org.springframework.cloud.stream.app:time-source-kafka:jar:metadata:@time-source.version@
processor.filter=maven://org.springframework.cloud.stream.app:filter-processor-kafka:@filter-processor.version@
processor.filter.metadata=maven://org.springframework.cloud.stream.app:filter-processor-kafka:jar:metadata:@filter-processor.version@
processor.splitter=maven://org.springframework.cloud.stream.app:splitter-processor-kafka:@splitter-processor.version@
processor.splitter.metadata=maven://org.springframework.cloud.stream.app:splitter-processor-kafka:jar:metadata:@splitter-processor.version@
processor.transform=maven://org.springframework.cloud.stream.app:transform-processor-kafka:@transform-processor.version@
processor.transform.metadata=maven://org.springframework.cloud.stream.app:transform-processor-kafka:jar:metadata:@transform-processor.version@

View File

@@ -0,0 +1,13 @@
sink.cassandra=docker:springcloudstream/cassandra-sink-rabbit:@cassandra-sink-docker.tag@
sink.counter=docker:springcloudstream/counter-sink-rabbit:@counter-sink-docker.tag@
sink.jdbc=docker:springcloudstream/jdbc-sink-rabbit:@jdbc-sink-docker.tag@
sink.log=docker:springcloudstream/log-sink-rabbit:@log-sink-docker.tag@
sink.mongodb=docker:springcloudstream/mongodb-sink-rabbit:@mongodb-sink-docker.tag@
sink.rabbit=docker:springcloudstream/rabbit-sink-rabbit:@rabbit-sink-docker.tag@
source.http=docker:springcloudstream/http-source-rabbit:@http-source-docker.tag@
source.jdbc=docker:springcloudstream/jdbc-source-rabbit:@jdbc-source-docker.tag@
source.mongodb=docker:springcloudstream/mongodb-source-rabbit:@mongodb-source-docker.tag@
source.time=docker:springcloudstream/time-source-rabbit:@time-source-docker.tag@
processor.filter=docker:springcloudstream/filter-processor-rabbit:@filter-processor-docker.tag@
processor.splitter=docker:springcloudstream/splitter-processor-rabbit:@splitter-processor-docker.tag@
processor.transform=docker:springcloudstream/transform-processor-rabbit:@transform-processor-docker.tag@

View File

@@ -0,0 +1,26 @@
sink.cassandra=https://@repo-spring-io@/org/springframework/cloud/stream/app/cassandra-sink-rabbit/@cassandra-sink.version@/cassandra-sink-rabbit-@cassandra-sink.version@.jar
sink.cassandra.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/cassandra-sink-rabbit/@cassandra-sink.version@/cassandra-sink-rabbit-@cassandra-sink.version@-metadata.jar
sink.counter=https://@repo-spring-io@/org/springframework/cloud/stream/app/counter-sink-rabbit/@counter-sink.version@/counter-sink-rabbit-@counter-sink.version@.jar
sink.counter.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/counter-sink-rabbit/@counter-sink.version@/counter-sink-rabbit-@counter-sink.version@-metadata.jar
sink.jdbc=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-sink-rabbit/@jdbc-sink.version@/jdbc-sink-rabbit-@jdbc-sink.version@.jar
sink.jdbc.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-sink-rabbit/@jdbc-sink.version@/jdbc-sink-rabbit-@jdbc-sink.version@-metadata.jar
sink.log=https://@repo-spring-io@/org/springframework/cloud/stream/app/log-sink-rabbit/@log-sink.version@/log-sink-rabbit-@log-sink.version@.jar
sink.log.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/log-sink-rabbit/@log-sink.version@/log-sink-rabbit-@log-sink.version@-metadata.jar
sink.mongodb=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-sink-rabbit/@mongodb-sink.version@/mongodb-sink-rabbit-@mongodb-sink.version@.jar
sink.mongodb.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-sink-rabbit/@mongodb-sink.version@/mongodb-sink-rabbit-@mongodb-sink.version@-metadata.jar
sink.rabbit=https://@repo-spring-io@/org/springframework/cloud/stream/app/rabbit-sink-rabbit/@rabbit-sink.version@/rabbit-sink-rabbit-@rabbit-sink.version@.jar
sink.rabbit.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/rabbit-sink-rabbit/@rabbit-sink.version@/rabbit-sink-rabbit-@rabbit-sink.version@-metadata.jar
source.http=https://@repo-spring-io@/org/springframework/cloud/stream/app/http-source-rabbit/@http-source.version@/http-source-rabbit-@http-source.version@.jar
source.http.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/http-source-rabbit/@http-source.version@/http-source-rabbit-@http-source.version@-metadata.jar
source.jdbc=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-source-rabbit/@jdb-source.version@/jdbc-source-rabbit-@jdbc-source.version@.jar
source.jdbc.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/jdbc-source-rabbit/@jdb-source.version@/jdbc-source-rabbit-@jdbc-source.version@-metadata.jar
source.mongodb=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-source-rabbit/@mongodb-source.version@/mongodb-source-rabbit-@mongodb-source.version@.jar
source.mongodb.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/mongodb-source-rabbit/@mongodb-source.version@/mongodb-source-rabbit-@mongodb-source.version@-metadata.jar
source.time=https://@repo-spring-io@/org/springframework/cloud/stream/app/time-source-rabbit/@time-source.version@/time-source-rabbit-@time-source.version@.jar
source.time.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/time-source-rabbit/@time-source.version@/time-source-rabbit-@time-source.version@-metadata.jar
processor.filter=https://@repo-spring-io@/org/springframework/cloud/stream/app/filter-processor-rabbit/@filter-processor.version@/filter-processor-rabbit-@filter-processor.version@.jar
processor.filter.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/filter-processor-rabbit/@filter-processor.version@/filter-processor-rabbit-@filter-processor.version@-metadata.jar
processor.splitter=https://@repo-spring-io@/org/springframework/cloud/stream/app/splitter-processor-rabbit/@splitter-processor.version@/splitter-processor-rabbit-@splitter-processor.version@.jar
processor.splitter.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/splitter-processor-rabbit/@splitter-processor.version@/splitter-processor-rabbit-@splitter-processor.version@-metadata.jar
processor.transform=https://@repo-spring-io@/org/springframework/cloud/stream/app/transform-processor-rabbit/@transform-processor.version@/transform-processor-rabbit-@transform-processor.version@.jar
processor.transform.metadata=https://@repo-spring-io@/org/springframework/cloud/stream/app/transform-processor-rabbit/@transform-processor.version@/transform-processor-rabbit-@transform-processor.version@-metadata.jar

Some files were not shown because too many files have changed in this diff Show More