diff --git a/spring-cloud-dataflow-apps-plugin/.gitignore b/spring-cloud-dataflow-apps-plugin/.gitignore new file mode 100644 index 00000000..d9338952 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/.gitignore @@ -0,0 +1,29 @@ +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 +.vscode/ + diff --git a/spring-cloud-dataflow-apps-plugin/.mvn/jvm.config b/spring-cloud-dataflow-apps-plugin/.mvn/jvm.config new file mode 100644 index 00000000..0e7dabef --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom \ No newline at end of file diff --git a/spring-cloud-dataflow-apps-plugin/.mvn/maven.config b/spring-cloud-dataflow-apps-plugin/.mvn/maven.config new file mode 100644 index 00000000..3b8cf46e --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/.mvn/maven.config @@ -0,0 +1 @@ +-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring diff --git a/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/MavenWrapperDownloader.java b/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..e76d1f32 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/MavenWrapperDownloader.java @@ -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 + * + * 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. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..642d572c --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/spring-cloud-dataflow-apps-plugin/LICENSE b/spring-cloud-dataflow-apps-plugin/LICENSE new file mode 100644 index 00000000..9b259bdf --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/LICENSE @@ -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. diff --git a/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle-header.txt b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle-header.txt new file mode 100644 index 00000000..39429ee6 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle-header.txt @@ -0,0 +1,17 @@ +^\Q/*\E$ +^\Q * Copyright \E20\d\d\-20\d\d\Q the original author or authors.\E$ +^\Q *\E$ +^\Q * Licensed under the Apache License, Version 2.0 (the "License");\E$ +^\Q * you may not use this file except in compliance with the License.\E$ +^\Q * You may obtain a copy of the License at\E$ +^\Q *\E$ +^\Q * https://www.apache.org/licenses/LICENSE-2.0\E$ +^\Q *\E$ +^\Q * Unless required by applicable law or agreed to in writing, software\E$ +^\Q * distributed under the License is distributed on an "AS IS" BASIS,\E$ +^\Q * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\E$ +^\Q * See the License for the specific language governing permissions and\E$ +^\Q * limitations under the License.\E$ +^\Q */\E$ +^$ +^.*$ diff --git a/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle-suppressions.xml b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle-suppressions.xml new file mode 100644 index 00000000..9d3a0b2d --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle.xml b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle.xml new file mode 100644 index 00000000..c3e1de40 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/checkstyle.xml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/etc/checkstyle/nohttp-checkstyle.xml b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/nohttp-checkstyle.xml new file mode 100644 index 00000000..4e21a0bd --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/etc/checkstyle/nohttp-checkstyle.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-dataflow-apps-plugin/mvnw b/spring-cloud-dataflow-apps-plugin/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-cloud-dataflow-apps-plugin/mvnw.cmd b/spring-cloud-dataflow-apps-plugin/mvnw.cmd new file mode 100644 index 00000000..86115719 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/spring-cloud-dataflow-apps-plugin/pom.xml b/spring-cloud-dataflow-apps-plugin/pom.xml new file mode 100644 index 00000000..a4a0084b --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/pom.xml @@ -0,0 +1,289 @@ + + + 4.0.0 + org.springframework.cloud + spring-cloud-dataflow-apps-plugin-parent + 1.0.8-SNAPSHOT + spring-cloud-dataflow-apps-plugin-parent + Spring Cloud Dataflow Apps Plugin Parent + pom + + + 17 + 3.1.1 + 3.2.1 + 2.22.2 + UTF-8 + UTF-8 + ${java.version} + ${java.version} + 3.0.1 + 3.1.0 + false + true + true + true + 8.29 + https://raw.githubusercontent.com/spring-cloud/spring-cloud-dataflow-apps-plugin/master/etc/checkstyle + + ${checkstyle.location}/checkstyle-suppressions.xml + + + ${checkstyle.location}/nohttp-checkstyle.xml + + + ${checkstyle.location}/checkstyle-suppressions.xml + + 0.0.2.RELEASE + true + 0.0.7 + + + + spring-cloud-dataflow-apps-generator-plugin + spring-cloud-dataflow-apps-metadata-plugin + spring-cloud-dataflow-apps-docs-plugin + + https://github.com/spring-cloud/spring-cloud-dataflow-apps-plugin + + + scdf-apps-plugin-developers + SCDF Apps Plugin Developers + chackos at vmware com + VMware + https://www.spring.io + + lead + + + + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + 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. + + + + scm:git:git://github.com/spring-cloud/spring-cloud-dataflow-apps-plugin.git + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-dataflow-apps-plugin.git + https://github.com/spring-cloud/spring-cloud-dataflow-apps-plugin + + + + + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + javadoc + package + + jar + + + + + 1.8 + true + + + + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + package + + jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + **/*Tests.java + **/*Test.java + + + **/Abstract*.java + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${puppycrawl-tools-checkstyle.version} + + + io.spring.javaformat + spring-javaformat-checkstyle + ${spring-javaformat-checkstyle.version} + + + io.spring.nohttp + nohttp-checkstyle + ${nohttp-checkstyle.version} + + + + + checkstyle-validation + validate + true + + ${disable.checks} + ${checkstyle.location}/checkstyle.xml + ${checkstyle.location}/checkstyle-header.txt + + checkstyle.build.directory=${project.build.directory} + checkstyle.suppressions.file=${checkstyle.suppressions.file} + checkstyle.additional.suppressions.file=${checkstyle.additional.suppressions.file} + + true + + ${maven-checkstyle-plugin.includeTestSourceDirectory} + + ${maven-checkstyle-plugin.failsOnError} + + + ${maven-checkstyle-plugin.failOnViolation} + + + + check + + + + no-http-checkstyle-validation + validate + true + + ${disable.nohttp.checks} + ${checkstyle.nohttp.file} + **/* + **/.idea/**/*,**/.git/**/*,**/target/**/*,**/*.log + ./ + + + check + + + + + + + + + repo.spring.io + Spring Release Repository + https://repo.spring.io/libs-release-local + + + repo.spring.io + Spring Snapshot Repository + https://repo.spring.io/libs-snapshot-local + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + + + + + milestone + + + repo.spring.io + Spring Milestone Repository + https://repo.spring.io/libs-milestone-local + + + + + central + + + + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + sonatype-nexus-staging + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/README.adoc b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/README.adoc new file mode 100644 index 00000000..cbd50246 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/README.adoc @@ -0,0 +1,44 @@ += Spring Cloud Stream & Task Maven Documentation Plugin + +Maven plugin for generating documentation for Spring Cloud stream and task app starters. + +Assuming a Spring Cloud Stream / Task app (or app starter) has +specified selected configuration properties to be visible (i.e., providing src/main/resources/META-INF/dataflow-configuration-metadata.properties), +this plugin will help automate the documentation of such properties. +https://docs.spring.io/spring-cloud-dataflow/docs/1.1.0.M2/reference/html/spring-cloud-dataflow-register-apps.html#spring-cloud-dataflow-stream-app-whitelisting[whitelisted] +some configuration properties, this plugin will help automate the documentation of such properties. + +== Usage + +To use this plugin, simply add the following markers to your project `README.adoc` file: + +``` +//tag::configuration-properties[] +//end::configuration-properties[] +``` + + +Then, configure this plugin for your app project (either directly or through a parent POM): +``` + + + + org.springframework.cloud.stream.app.plugin + spring-cloud-stream-app-documentation-maven-plugin + 1.0.0.BUILD-SNAPSHOT + + + generate-documentation + verify + + generate-documentation + + + + + + +``` + +Documentation for the visible properties shall appear on next build, which should be committed under VCS. + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/pom.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/pom.xml new file mode 100644 index 00000000..b01b48f9 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-dataflow-apps-plugin-parent + 1.0.8-SNAPSHOT + .. + + + spring-cloud-dataflow-apps-docs-plugin + maven-plugin + + + + org.apache.maven + maven-plugin-api + 3.5.2 + + + org.apache.maven + maven-core + 3.5.2 + + + org.springframework.cloud + spring-cloud-dataflow-configuration-metadata + 2.7.0 + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.4 + provided + + + + junit + junit + 4.12 + test + + + org.assertj + assertj-core + 3.15.0 + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + spring + + + + true + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/ConfigurationMetadataDocumentationMojo.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/ConfigurationMetadataDocumentationMojo.java new file mode 100644 index 00000000..5371f78e --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/ConfigurationMetadataDocumentationMojo.java @@ -0,0 +1,424 @@ +/* + * Copyright 2016-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.dataflow.app.plugin; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.jar.Manifest; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DependencyResolutionRequiredException; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.ExplodedArchive; +import org.springframework.boot.loader.archive.JarFileArchive; +import org.springframework.cloud.dataflow.configuration.metadata.BootApplicationConfigurationMetadataResolver; +import org.springframework.cloud.dataflow.configuration.metadata.BootClassLoaderFactory; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * A maven plugin that will scan an asciidoc file for special comment markers and replace + * everything in between with a listing of visible configuration properties for a Spring + * Cloud Stream/Task app. Applications with multiple properties prefixes are grouped by default. + * This can be disabled by using {@code //tag::configuration-properties[group=false]}. + * + * @author Eric Bottard + * @author David Turanski + * @see Whitelisting + * Properties + */ +@Mojo(name = "generate-documentation", requiresDependencyResolution = ResolutionScope.RUNTIME) +public class ConfigurationMetadataDocumentationMojo extends AbstractMojo { + + static final String CONFIGURATION_PROPERTIES_START_TAG = "//tag::configuration-properties["; + + static final String CONFIGURATION_PROPERTIES_END_TAG = "//end::configuration-properties[]"; + + private BootApplicationConfigurationMetadataResolver metadataResolver = new BootApplicationConfigurationMetadataResolver( + imageName -> null); + + @Parameter(defaultValue = "${project}") + private MavenProject mavenProject; + + @Parameter(defaultValue = "false") + private boolean failOnMissingDescription; + + private boolean grouped = true; + + public void execute() throws MojoExecutionException { + + File readme = new File(mavenProject.getBasedir(), "README.adoc"); + if (!readme.exists()) { + getLog().info(String.format("No README.adoc file found in %s, skipping", mavenProject.getBasedir())); + return; + } + + Artifact artifact = mavenProject.getArtifact(); + if (artifact.getFile() == null) { + getLog().info(String.format("Project in %s does not produce a build artifact, skipping", + mavenProject.getBasedir())); + return; + } + + File tmp = new File(readme.getPath() + ".tmp"); + try (PrintWriter out = new PrintWriter(tmp); + BufferedReader reader = new BufferedReader(new FileReader(readme))) { + + String line; + do { + line = reader.readLine(); + out.println(line); + } + while (line != null && !line.startsWith(CONFIGURATION_PROPERTIES_START_TAG)); + if (line == null) { + getLog().info("No documentation section marker found"); + return; + } + + Map startTagAttributes = startTagAttributes(line); + + if ("false".equals(startTagAttributes.get("group"))) { + grouped = false; + } + + ScatteredArchive archive = new ScatteredArchive(mavenProject); + BootClassLoaderFactory bootClassLoaderFactory = new BootClassLoaderFactory(archive, null); + try (URLClassLoader classLoader = bootClassLoaderFactory.createClassLoader()) { + debug(classLoader); + + List properties = metadataResolver.listProperties(archive, false); + Collections.sort(properties, Comparator.comparing(ConfigurationMetadataProperty::getId)); + + Map> groupedProperties = groupProperties(properties); + + grouped = grouped && groupedProperties.size() > 1; + + if (grouped) { + out.println("Properties grouped by prefix:\n"); + groupedProperties.forEach((group, props) -> { + getLog().debug(" Documenting group " + group); + out.println(asciidocForGroup(group)); + listProperties(props, out, classLoader, prop -> prop.getName()); + }); + + } + else { + listProperties(properties, out, classLoader, prop -> prop.getId()); + } + do { + line = reader.readLine(); + // drop lines + } + while (!line.startsWith(CONFIGURATION_PROPERTIES_END_TAG)); + + // Copy remaining lines, including //end::configuration-properties[] + while (line != null) { + out.println(line); + line = reader.readLine(); + } + getLog().info(String.format("Documented %d configuration properties", properties.size())); + } + } + catch (Exception e) { + tmp.delete(); + throw new MojoExecutionException("Error generating documentation", e); + } + + try { + Files.move(tmp.toPath(), readme.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException e) { + throw new MojoExecutionException("Error moving tmp file to README.adoc", e); + } + } + + private void listProperties(List properties, PrintWriter out, + ClassLoader classLoader, + Function propertyValue) { + for (ConfigurationMetadataProperty property : properties) { + getLog().debug("Documenting " + property.getId()); + out.println(asciidocFor(property, classLoader, propertyValue)); + } + + } + + private Map> groupProperties( + List properties) { + Map> groupedProperties = new LinkedHashMap<>(); + properties.forEach(property -> { + String group = group(property.getId()); + if (!groupedProperties.containsKey(group)) { + groupedProperties.put(group, new LinkedList<>()); + } + groupedProperties.get(group).add(property); + }); + return groupedProperties; + } + + private String group(String id) { + return id.lastIndexOf('.') > 0 ? id.substring(0, id.lastIndexOf('.')) : ""; + } + + private void debug(ClassLoader classLoader) { + if (classLoader instanceof URLClassLoader) { + List urls = Arrays.asList(((URLClassLoader) classLoader).getURLs()); + getLog().debug("Classloader has the following URLs:\n" + urls.toString().replace(',', '\n')); + } + } + + private String asciidocFor(ConfigurationMetadataProperty property, ClassLoader classLoader, + Function propertyValue) { + return String.format("$$%s$$:: $$%s$$ *($$%s$$, default: `$$%s$$`%s)*", + propertyValue.apply(property), + niceDescription(property), + niceType(property), + niceDefault(property), + maybeHints(property, classLoader)); + } + + private String asciidocForGroup(String group) { + return "\n=== " + group + "\n"; + } + + private Map startTagAttributes(String startTag) { + String attrs = startTag.substring(startTag.indexOf('[') + 1, startTag.indexOf(']')); + Set set = StringUtils.commaDelimitedListToSet(attrs); + Map attributes = new LinkedHashMap<>(); + set.forEach(attr -> { + String[] keyValue = attr.split("="); + attributes.put(keyValue[0].trim(), keyValue.length == 2 ? keyValue[1].trim() : ""); + }); + return attributes; + } + + private String niceDescription(ConfigurationMetadataProperty property) { + if (property.getDescription() == null) { + if (failOnMissingDescription) { + throw new RuntimeException("Missing description for property " + property.getId()); + } + else { + return ""; + } + } + return property.getDescription(); + } + + private CharSequence maybeHints(ConfigurationMetadataProperty property, ClassLoader classLoader) { + String type = property.getType(); + if (type == null) { + return ""; + } + + type = type.replace('$', '.'); + if (ClassUtils.isPresent(type, classLoader)) { + Class clazz = ClassUtils.resolveClassName(type, classLoader); + if (clazz.isEnum()) { + return ", possible values: `" + StringUtils.arrayToDelimitedString(clazz.getEnumConstants(), "`,`") + + "`"; + } + } + return ""; + } + + private String niceDefault(ConfigurationMetadataProperty property) { + if (property.getDefaultValue() == null) { + return ""; + } + else if ("".equals(property.getDefaultValue())) { + return ""; + } + else { + return stringify(property.getDefaultValue()); + } + } + + private String stringify(Object element) { + Class clazz = element.getClass(); + if (clazz == byte[].class) { + return Arrays.toString((byte[]) element); + } + else if (clazz == short[].class) { + return Arrays.toString((short[]) element); + } + else if (clazz == int[].class) { + return Arrays.toString((int[]) element); + } + else if (clazz == long[].class) { + return Arrays.toString((long[]) element); + } + else if (clazz == char[].class) { + return Arrays.toString((char[]) element); + } + else if (clazz == float[].class) { + return Arrays.toString((float[]) element); + } + else if (clazz == double[].class) { + return Arrays.toString((double[]) element); + } + else if (clazz == boolean[].class) { + return Arrays.toString((boolean[]) element); + } + else if (element instanceof Object[]) { + return Arrays.deepToString((Object[]) element); + } + else { + return element.toString(); + } + } + + private String niceType(ConfigurationMetadataProperty property) { + String type = property.getType(); + if (type == null) { + return ""; + } + return niceType(type); + } + + String niceType(String type) { + List parts = new ArrayList<>(); + int openBrackets = 0; + int lastGenericPart = 0; + for (int i = 0; i < type.length(); i++) { + switch (type.charAt(i)) { + case '<': + if (openBrackets++ == 0) { + parts.add(type.substring(0, i)); + lastGenericPart = i + 1; + } + break; + case '>': + if (--openBrackets == 0) { + parts.add(type.substring(lastGenericPart, i)); + } + break; + case ',': + if (openBrackets == 1) { + parts.add(type.substring(lastGenericPart, i)); + lastGenericPart = i + 1; + } + break; + case ' ': + if (openBrackets == 1) { + lastGenericPart++; + } + break; + } + } + if (parts.isEmpty()) { + return unqualify(type); // simple type + } + else { // type with generics + StringBuilder sb = new StringBuilder(unqualify(parts.get(0))); + for (int i = 1; i < parts.size(); i++) { + if (i == 1) { + sb.append('<'); + } + sb.append(unqualify(niceType(parts.get(i)))); + if (i == parts.size() - 1) { + sb.append('>'); + } + else { + sb.append(", "); + } + } + return sb.toString(); + } + } + + private String unqualify(String type) { + int lastDot = type.lastIndexOf('.'); + int lastDollar = type.lastIndexOf('$'); + return type.substring(Math.max(lastDot, lastDollar) + 1); + } + + /** + * An adapter to boot {@link Archive} that satisfies just enough of the API to craft a + * ClassLoader that "sees" all the properties that this Mojo tries to document. + * @author Eric Bottard + */ + private static final class ScatteredArchive implements Archive { + + private final MavenProject mavenProject; + + private ScatteredArchive(MavenProject mavenProject) { + + this.mavenProject = mavenProject; + } + + @Override + public URL getUrl() throws MalformedURLException { + return mavenProject.getArtifact().getFile().toURI().toURL(); + } + + @Override + public Manifest getManifest() { + throw new UnsupportedOperationException(); + } + + @Override + public List getNestedArchives(EntryFilter ignored) throws IOException { + try { + List archives = new ArrayList<>(mavenProject.getRuntimeClasspathElements().size()); + for (String dep : mavenProject.getRuntimeClasspathElements()) { + File file = new File(dep); + archives.add(file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file)); + } + return archives; + } + catch (DependencyResolutionRequiredException e) { + throw new IOException("Could not create boot archive", e); + } + + } + + @Override + public Iterator iterator() { + // BootClassLoaderFactory.createClassLoader (which uses this iterator call) is not + // actually + // used here. Returning the simples thing that works. + return Collections.emptyIterator(); + } + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/ConfigurationMetadataDocumentationMojoTests.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/ConfigurationMetadataDocumentationMojoTests.java new file mode 100644 index 00000000..f5b72ced --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-docs-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/ConfigurationMetadataDocumentationMojoTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017-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.dataflow.app.plugin; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConfigurationMetadataDocumentationMojoTests { + + @Test + public void testJavaTypeBeautifier() { + ConfigurationMetadataDocumentationMojo mojo = new ConfigurationMetadataDocumentationMojo(); + String s = mojo.niceType("java.lang.String"); + assertThat(s).isEqualTo("String"); + + s = mojo.niceType("java.lang.Class"); + assertThat(s).isEqualTo("Class"); + + s = mojo.niceType("java.util.Map$Entry>>"); + assertThat(s).isEqualTo("Entry>>"); + } + +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/README.adoc b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/README.adoc new file mode 100644 index 00000000..e7cf3592 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/README.adoc @@ -0,0 +1,302 @@ += spring-cloud-dataflow-apps-generator-plugin + +Maven plugin for generating spring cloud stream applications: https://github.com/spring-cloud/stream-applications +It generates a separate maven project for every configured binder. + +Plugin `Application` section helps to configure the application project independently form the Binders that +can be used. The `Binders` section helps to define the Binder specific configurations, that are merged to +the `Application` configurations to generate the binder specific maven projects. + +The `Global` section (e.g. `global.application` and `global.binders`) helps to define configurations sharable +between many Applications and Binders. + +As the following diagram illustrates, both the `Application` and the +`Binder` allow configuring various application and maven properties: + +image::src/main/resources/doc/MojoConfiguration.jpg[] + +== Build + +mvn clean package [Requires JDK 8] + +== Sample Configuration for generating spring cloud stream apps + +In the parent POM define the `Application` and `Binder` configurations common for all applications to be generated. +Do this under the `pluginManagement` pom section and use the plugin's `global` configuration: + +[source, xml] +---- + + + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + 1.0.0-SNAPSHOT + + + app-gen + package + + generate-app + + + + + ${basedir}/apps + + + + + ${spring-boot.version} + + + true + + + + 2.0.2.BUILD-SNAPSHOT + + + + + + org.springframework.cloud.fn + function-dependencies + ${java-functions.version} + + + org.springframework.cloud + spring-cloud-stream-dependencies + ${spring-cloud-stream-dependencies.version} + + + org.springframework.cloud + spring-cloud-function-dependencies + ${spring-cloud-function-dependencies.version} + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + + + + + org.springframework.cloud.stream.app + stream-applications-security-common + + + org.springframework.cloud.stream.app + stream-applications-micrometer-common + + + org.springframework.cloud.stream.app + stream-applications-postprocessor-common + + + io.micrometer + micrometer-registry-prometheus + + + io.micrometer.prometheus + prometheus-rsocket-spring + + + io.micrometer + micrometer-registry-wavefront + + + io.pivotal.cfenv + java-cfenv-boot + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-security + + + + + + + + + + org.springframework.cloud + spring-cloud-stream-binder-kafka + + + + + + + + + org.springframework.cloud + spring-cloud-stream-binder-rabbit + + + + + + + + + + +---- + +Above global configurations will be reused by all children applications. + +In addition, the child application can add application specific configurations, contributed to the inherited from +to the global: + +[source, xml] +---- + + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + 1.0.0-SNAPSHOT + + + syslog + source + ${project.version} + org.springframework.cloud.fn.supplier.syslog.SyslogSupplierConfiguration.class + + + + + org.springframework.cloud.fn + syslog-supplier + + + org.springframework.cloud.stream.app + stream-applications-composite-function-support + ${stream-apps-core.version} + + + + + + + ... + +---- + + +== Configure Solace and Pubsub binders support + +To generate applications for https://github.com/SolaceProducts/spring-cloud-stream-binder-solace[Solace] binders, extend your `binders` section with following configuration: + +[source, xml] +---- + + .... + + + + + com.solace.spring.cloud + spring-cloud-starter-stream-solace + 1.2.1 + + + + + .... + +---- + +You can add it either to your application's POM or to the parent's `global` POM sections. + +Similar to generate application for the https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-pubsub-stream-binder[PubSub] +binders and to your binders section configuration like this: + +[source, xml] +---- + +... + + + + + org.springframework.cloud + spring-cloud-gcp-dependencies + 1.2.4.RELEASE + + + + + org.springframework.cloud + spring-cloud-gcp-pubsub-stream-binder + + + + +... + +---- + +== Custom Spring Boot Maven Plugin Configuration + +Spring Boot Maven plugin offers a number of customization options by specifying a configuration section in the plugin definition. +For example, using the custom configuration, you can instruct the plugin to unpack a particular dependency. +You would do that using Spring Boot Maven plugin as below. +``` + + org.springframework.boot + spring-boot-maven-plugin + + + + org.python + jython-standalone + + + + +``` + +When using the app generator plugin, you can ask the plugin to include this information when it generates the necessary XML snippets for adding the Spring Boot Maven plugin. +Below is an example of doing so. +``` + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + + + ... + + + + org.python + jython-standalone + + + ]]> + + ... + + +``` + +Pay attention to how the Spring Boot plugin configuration is wrapped inside a `CDATA` element. +This is necessary for the app generator to properly parse this information. \ No newline at end of file diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/pom.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/pom.xml new file mode 100644 index 00000000..9c0b289e --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/pom.xml @@ -0,0 +1,166 @@ + + + 4.0.0 + + spring-cloud-dataflow-apps-generator-plugin + maven-plugin + spring-cloud-dataflow-apps-generator-plugin + + + org.springframework.cloud + spring-cloud-dataflow-apps-plugin-parent + 1.0.8-SNAPSHOT + .. + + + + UTF-8 + 3.9.2 + 3.9.0 + 1.15 + 6.0.8 + 2.12.0 + + + + + com.samskivert + jmustache + ${jmustache.version} + + + + org.springframework + spring-core + ${spring.version} + + + + org.apache.maven + maven-plugin-api + provided + ${maven.version} + + + org.apache.maven + maven-core + ${maven.version} + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven.plugin.version} + provided + + + + commons-io + commons-io + ${commons.io.version} + + + + + junit + junit + 4.13 + test + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + 3.3.0 + test + + + org.apache.maven + maven-compat + ${maven.version} + test + + + org.assertj + assertj-core + 3.15.0 + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven.plugin.version} + + generate-app + true + + + + mojo-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + run-its + + + + org.apache.maven.plugins + maven-invoker-plugin + 3.2.1 + + true + ${project.build.directory}/it + + */pom.xml + + verify + ${project.build.directory}/local-repo + src/it/settings.xml + + clean + test-compile + + + + + integration-test + + install + integration-test + verify + + + + + + + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MavenXmlWriter.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MavenXmlWriter.java new file mode 100644 index 00000000..8294dde2 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MavenXmlWriter.java @@ -0,0 +1,173 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin; + +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Method; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.maven.model.Plugin; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.codehaus.plexus.util.xml.pull.MXSerializer; +import org.codehaus.plexus.util.xml.pull.XmlSerializer; + +/** + * Uses the private MavenXpp3Writer write methods to convert Model elements into XML strings. + * + * @author Christian Tzolov + */ +public final class MavenXmlWriter { + + private MavenXmlWriter() { + + } + + /** + * Serializes any instance of a e.g. org.apache.maven.model.XXXX class into XML text. + * Via reflections calls the private MavenXpp3Writer#writeXXXX(XXXX, String, XmlSerializer) method. + * As xml tag uses the lower case of the XXXX class name (e.g. xxxx). + *

+ * If above name conventions don't hold or additional processing is required implement a dedicated overload method. + * For example see {@link #toXml(Plugin)} + * + * @param element instance of a org.apache.maven.model.* class. + * @param element type. + * @return XML text serialization of the element. + */ + public static String toXml(T element) { + String privateMethodName = "write" + element.getClass().getSimpleName(); + String xmlTagName = element.getClass().getSimpleName().toLowerCase(); + return write(serializer -> invokeMavenXppWriteMethod(element, privateMethodName, xmlTagName, serializer)); + } + + /** + * Serializes an {@link Plugin} instance into XML text, using reflection to invoke the private + * MavenXpp3Writer#writePlugin(Plugin, String, XmlSerializer) method. The Plugin Configuration block (when present) + * requires special handling to convert String values into Xpp3Dom object before writing. + *

+ * For Plugin Configuration you must nest the configuration content into a CDATA block like shown here: + *

{@code
+	 * 
+	 *   
+	 *     com.google.cloud.tools
+	 *     jib-maven-plugin
+	 *     3.3.0
+	 *     
+	 *       springcloud/baseimage:1.0.4
+	 *         
+	 *           springcloudstream:${project.artifactId}
+	 *           latest
+	 *         
+	 *         
+	 *           Docker
+	 *         
+	 *       ]]>
+	 *     
+	 *   
+	 * 
+	 * }
+ * + * @param plugin Plugin instance to serialize. + * @return POM/XML serialization of the plugin. + */ + public static String toXml(Plugin plugin) { + try { + Object configuration = plugin.getConfiguration(); + if (configuration != null && !(configuration instanceof Xpp3Dom)) { + plugin.setConfiguration(Xpp3DomBuilder.build(new StringReader( + "" + configuration.toString() + ""))); + } + } + catch (Exception ex) { + throw new IllegalStateException("Issue creating config for additional plugin", ex); + } + + return write(serializer -> invokeMavenXppWriteMethod( + plugin, "writePlugin", "plugin", serializer)); + } + + /** + * Template method that helps generating well-formed and valid XML segments. It uses the {@link XmlSerializer} + * to create the create the XML structure and delegates to the #elementWriter callback to fill in the content. + * + * @param elementWriter Callback handler that contributes the XML content. + * @return Returns well-formed XML element. + */ + public static String write(Consumer elementWriter) { + try { + Writer writer = new StringWriter(); + + XmlSerializer serializer = new MXSerializer(); + serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", " "); + serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-line-separator", "\n"); + serializer.setOutput(writer); + + serializer.startDocument("UTF-8", null); + elementWriter.accept(serializer); + serializer.endDocument(); + + String result = writer.toString(); + return result.substring(result.indexOf('\n') + 1); // remove first line (e.g. remove the ) + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * Uses the private MavenXpp3Writer methods to convert Model elements into XML strings. + * + * @param modelElementToWrite a parameterizable org.apache.maven.model.T instance to serialize. + * @param The actual org.apache.maven.model's class type. + * @param writeMethodName Name of the private {@link MavenXpp3Writer} method to call. + * @param xmlTagName Name for the XML tag to surround the serialized model instance. + * @param serializer The XmlSerializer used to write the xml. + */ + public static void invokeMavenXppWriteMethod(T modelElementToWrite, String writeMethodName, + String xmlTagName, XmlSerializer serializer) { + + try { + MavenXpp3Writer pomWriter = new MavenXpp3Writer(); + Method method = pomWriter.getClass().getDeclaredMethod( + writeMethodName, modelElementToWrite.getClass(), String.class, XmlSerializer.class); + method.setAccessible(true); // allow invoking private method. + method.invoke(pomWriter, modelElementToWrite, xmlTagName, serializer); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * Indent the input string by the provided indentation steps. + * @param input String to indent. + * @param indentation Number of indentation steps. + * @return Returns the formatted string. + */ + public static String indent(String input, int indentation) { + String indentPrefix = "\n" + IntStream.range(0, indentation).mapToObj(i -> " ").collect(Collectors.joining()); + String indentedInput = input.replace("\n", indentPrefix); + return indentedInput.substring(0, indentedInput.lastIndexOf(indentPrefix)); // remove the last empty line. + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/SpringCloudStreamAppGeneratorMojo.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/SpringCloudStreamAppGeneratorMojo.java new file mode 100644 index 00000000..ecd80e2d --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/SpringCloudStreamAppGeneratorMojo.java @@ -0,0 +1,780 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.Repository; +import org.apache.maven.model.RepositoryBase; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +import org.springframework.cloud.dataflow.app.plugin.generator.AppDefinition; +import org.springframework.cloud.dataflow.app.plugin.generator.BinderDefinition; +import org.springframework.cloud.dataflow.app.plugin.generator.ProjectGenerator; +import org.springframework.cloud.dataflow.app.plugin.generator.ProjectGeneratorProperties; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * @author Christian Tzolov + * @author David Turanski + * @author Soby Chacko + */ +@Mojo(name = "generate-app") +public class SpringCloudStreamAppGeneratorMojo extends AbstractMojo { + + private static final String DEPRECATED_WHITELIST_FILE_NAME = "dataflow-configuration-metadata-whitelist.properties"; + private static final String VISIBLE_PROPERTIES_FILE_NAME = "dataflow-configuration-metadata.properties"; + private static final String CONFIGURATION_PROPERTIES_CLASSES = "configuration-properties.classes"; + private static final String CONFIGURATION_PROPERTIES_NAMES = "configuration-properties.names"; + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + private MavenProject project; + + @Parameter(defaultValue = "${project.resources[0].directory}", readonly = true, required = true) + private File projectResourcesDir; + + @Parameter(defaultValue = "./apps", required = true) + private String generatedProjectHome; + + @Parameter + private Global global = new Global(); + + @Parameter(required = true) + private Application application; + + @Parameter + private Map binders = new HashMap<>(); + + @Override + public void execute() throws MojoFailureException, MojoExecutionException { + + // ---------------------------------------------------------------------------------------------------------- + // Application Configuration + // ---------------------------------------------------------------------------------------------------------- + + AppDefinition app = new AppDefinition(); + + String bootVersion = StringUtils.isEmpty(this.application.getBootVersion()) ? + this.global.getApplication().getBootVersion() : this.application.getBootVersion(); + if (StringUtils.isEmpty(bootVersion)) { + throw new MojoExecutionException("The application.bootVersion parameter is required!"); + } + app.setSpringBootVersion(bootVersion); + + String applicationName = StringUtils.isEmpty(this.application.getName()) ? + this.global.getApplication().getName() : this.application.getName(); + if (StringUtils.isEmpty(applicationName)) { + throw new MojoExecutionException("The application.name parameter is required!"); + } + app.setName(applicationName); + + AppDefinition.AppType applicationType = (this.application.getType() == null) ? + this.global.getApplication().getType() : this.application.getType(); + if (applicationType == null) { + throw new MojoExecutionException("The application.type parameter is required!"); + } + app.setType(applicationType); + + String applicationVersion = StringUtils.isEmpty(this.application.getVersion()) ? + this.global.getApplication().getVersion() : this.application.getVersion(); + if (StringUtils.isEmpty(applicationVersion)) { + throw new MojoExecutionException("The application.version parameter is required!"); + } + app.setVersion(applicationVersion); + + String applicationConfigClass = StringUtils.isEmpty(this.application.getConfigClass()) ? + this.global.getApplication().getConfigClass() : this.application.getConfigClass(); + app.setConfigClass(applicationConfigClass); // TODO is applicationConfigClass a required parameter? + + String applicationGroupId = StringUtils.isEmpty(this.application.getGroupId()) ? + this.global.getApplication().getGroupId() : this.application.getGroupId(); + app.setGroupId(applicationGroupId); // optional parameter + + String applicationFunctionDefinition = StringUtils.isEmpty(this.application.getFunctionDefinition()) ? + this.global.getApplication().getFunctionDefinition() : this.application.getFunctionDefinition(); + app.setFunctionDefinition(applicationFunctionDefinition); //TODO is applicationFunctionDefinition required? + + String metadataMavenPluginVersion = StringUtils.isEmpty(this.application.getMetadata().getMavenPluginVersion()) ? + this.global.getApplication().getMetadata().getMavenPluginVersion() : this.application.getMetadata().getMavenPluginVersion(); + if (StringUtils.isEmpty(metadataMavenPluginVersion)) { + throw new MojoExecutionException("The application.metadata.mavenPluginVersion parameter is required!"); + } + app.getMetadata().setMavenPluginVersion(metadataMavenPluginVersion); + + List allSourceTypeFilters = new ArrayList<>(this.global.getApplication().getMetadata().getSourceTypeFilters()); + allSourceTypeFilters.addAll(this.application.getMetadata().getSourceTypeFilters()); + List allNamedFilters = new ArrayList<>(this.global.getApplication().getMetadata().getNameFilters()); + allNamedFilters.addAll(this.application.getMetadata().getNameFilters()); + + this.populateVisiblePropertiesFromFile(allSourceTypeFilters, allNamedFilters); + + if (!CollectionUtils.isEmpty(allSourceTypeFilters)) { + app.getMetadata().setSourceTypeFilters(allSourceTypeFilters); + } + + if (!CollectionUtils.isEmpty(allNamedFilters)) { + app.getMetadata().setNameFilters(allNamedFilters); + } + + // Application properties + Map allApplicationProperties = new TreeMap<>(this.global.getApplication().getProperties()); + allApplicationProperties.putAll(this.application.getProperties()); + app.setProperties(allApplicationProperties.entrySet().stream() + .map(e -> e.getKey() + "=" + (StringUtils.hasText(e.getValue()) ? + e.getValue().replaceAll("^\"|\"$", "") : "")) + .collect(Collectors.toList())); + + // Maven properties + Map allMavenProperties = new TreeMap<>(this.global.getApplication().getMaven().getProperties()); + allMavenProperties.putAll(this.application.getMaven().getProperties()); + app.getMaven().setProperties(allMavenProperties.entrySet().stream() + .map(es -> "<" + es.getKey() + ">" + (StringUtils.hasText(es.getValue()) ? + es.getValue().replaceAll("^\"|\"$", "") : "") + "") + .collect(Collectors.toList())); + + //Maven BOM. For DependencyManagement it is important to retain the exact definition order! + // Override the global dependencies with matching app dependency definitions. Retain the definition order! + List allDeps = this.global.getApplication().getMaven().getDependencyManagement().stream() + .map(globalDep -> this.application.getMaven().getDependencyManagement().stream() + .filter(d -> isSameArtifact(globalDep, d)) + .findFirst().orElse(globalDep)) + .collect(Collectors.toList()); + + // Add remaining app dependencies that have not be used for overriding. Retain the order! + this.application.getMaven().getDependencyManagement() + .forEach(appDep -> { + if (allDeps.stream().noneMatch(d -> isSameArtifact(appDep, d))) { + allDeps.add(appDep); + } + }); + + app.getMaven().setDependencyManagement(allDeps.stream() + .filter(Objects::nonNull) + .peek(dependency -> { + dependency.setScope("import"); + dependency.setType("pom"); + }) + .map(MavenXmlWriter::toXml) + .map(xml -> MavenXmlWriter.indent(xml, 12)) + .collect(Collectors.toList())); + + //Maven Dependencies + Map allDependenciesMap = this.global.getApplication().getMaven().getDependencies().stream() + .collect(Collectors.toMap(d -> d.getGroupId() + ":" + d.getArtifactId(), d -> d)); + // Ensure that for dependencies with same maven coordinates that application definition overrides the global one. + allDependenciesMap.putAll(this.application.getMaven().getDependencies().stream() + .collect(Collectors.toMap(d -> d.getGroupId() + ":" + d.getArtifactId(), d -> d))); + app.getMaven().setDependencies(allDependenciesMap.values().stream() + .map(MavenXmlWriter::toXml) + .map(xml -> MavenXmlWriter.indent(xml, 8)) + .collect(Collectors.toList())); + + //Maven Plugins + Map allPluginsMap = this.global.getApplication().getMaven().getPlugins().stream() + .collect(Collectors.toMap(p -> p.getGroupId() + ":" + p.getArtifactId(), p -> p)); + // Ensure that for plugins with same maven coordinates that application definition overrides the global one. + allPluginsMap.putAll(this.application.getMaven().getPlugins().stream() + .collect(Collectors.toMap(p -> p.getGroupId() + ":" + p.getArtifactId(), p -> p))); + app.getMaven().setPlugins(allPluginsMap.values().stream() + .map(MavenXmlWriter::toXml) + .map(d -> MavenXmlWriter.indent(d, 12)) + .collect(Collectors.toList())); + + // Maven Repositories + Map allRepositoriesMap = this.global.getApplication().getMaven().getRepositories().stream() + .collect(Collectors.toMap(RepositoryBase::getId, r -> r)); + // Ensure that for repos with same maven coordinates that application definition overrides the global one. + allRepositoriesMap.putAll(this.application.getMaven().getRepositories().stream() + .collect(Collectors.toMap(RepositoryBase::getId, r -> r))); + app.getMaven().setRepositories(allRepositoriesMap.values().stream() + .map(MavenXmlWriter::toXml) + .map(d -> MavenXmlWriter.indent(d, 12)) + .collect(Collectors.toList())); + + // Container Image configuration + AppDefinition.ContainerImageFormat containerImageFormat = (this.application.getContainerImage().getFormat() != null) ? + this.application.getContainerImage().getFormat() : this.global.getApplication().getContainerImage().getFormat(); + containerImageFormat = (containerImageFormat == null) ? AppDefinition.ContainerImageFormat.Docker : containerImageFormat; + app.getContainerImage().setFormat(containerImageFormat); + + // TODO how to choose between global an app metadata enabling? + app.getContainerImage().setEnableMetadata(this.application.getContainerImage().isEnableMetadata()); + + if (StringUtils.hasText(this.application.getContainerImage().getOrgName())) { + app.getContainerImage().setOrgName(this.application.getContainerImage().getOrgName()); + } + else if (StringUtils.hasText(this.global.getApplication().getContainerImage().getOrgName())) { + app.getContainerImage().setOrgName(this.global.getApplication().getContainerImage().getOrgName()); + } + + app.getContainerImage().setTag(applicationVersion); + + if (StringUtils.hasText(this.application.getContainerImage().getBaseImage())) { + app.getContainerImage().setBaseImage(this.application.getContainerImage().getBaseImage()); + } + else if (StringUtils.hasText(this.global.getApplication().getContainerImage().getBaseImage())) { + app.getContainerImage().setBaseImage(this.global.getApplication().getContainerImage().getBaseImage()); + } + + // Generator Properties + ProjectGeneratorProperties generatorProperties = new ProjectGeneratorProperties(); + + // ---------------------------------------------------------------------------------------------------------- + // Binders Configuration + // ---------------------------------------------------------------------------------------------------------- + + Map allBinders = new HashMap<>(this.global.getBinders()); + // Note: The Application concrete Binder definitions will replace any global + // Binder definitions with the same names. E.g. you can replace but not extend global binders configurations. + allBinders.putAll(this.binders); + + if (CollectionUtils.isEmpty(allBinders)) { + throw new MojoExecutionException("At least one Binder configuration is required!"); + } + + // Converts Mojo's Binder configurations into BinderDefinitions. + List bindersDefinitions = allBinders.entrySet().stream() + .map(es -> { + BinderDefinition bd = new BinderDefinition(); + bd.setName(es.getKey()); + bd.setProperties(es.getValue().getProperties()); + bd.getMaven().setProperties(es.getValue().getMaven().getProperties() + .entrySet().stream() + .map(pes -> "<" + pes.getKey() + ">" + pes.getValue() + "") + .collect(Collectors.toList())); + bd.getMaven().setDependencies( + es.getValue().getMaven().getDependencies().stream() + .map(MavenXmlWriter::toXml) + .map(xml -> MavenXmlWriter.indent(xml, 8)) + .collect(Collectors.toList())); + bd.getMaven().setDependencyManagement( + es.getValue().getMaven().getDependencyManagement().stream() + .filter(Objects::nonNull) + .peek(dependency -> { + dependency.setScope("import"); + dependency.setType("pom"); + }) + .map(MavenXmlWriter::toXml) + .map(xml -> MavenXmlWriter.indent(xml, 12)) + .collect(Collectors.toList())); + bd.getMaven().setPlugins( + es.getValue().getMaven().getPlugins().stream() + .map(MavenXmlWriter::toXml) + .map(d -> MavenXmlWriter.indent(d, 12)) + .collect(Collectors.toList())); + bd.getMaven().setRepositories(es.getValue().getMaven().getRepositories().stream() + .map(MavenXmlWriter::toXml) + .map(d -> MavenXmlWriter.indent(d, 12)) + .collect(Collectors.toList())); + + return bd; + }) + .collect(Collectors.toList()); + + app.setBootPluginConfiguration(this.application.getBootPluginConfiguration()); + + // ---------------------------------------------------------------------------------------------------------- + // Project Generator + // ---------------------------------------------------------------------------------------------------------- + + generatorProperties.setAppDefinition(app); + generatorProperties.setBinderDefinitions(bindersDefinitions); + generatorProperties.setOutputFolder(new File(this.generatedProjectHome)); + generatorProperties.setProjectResourcesDirectory(this.projectResourcesDir); + + try { + ProjectGenerator.getInstance().generate(generatorProperties); + } + catch (IOException e) { + throw new MojoFailureException("Project generation failure", e); + } + } + + private boolean isSameArtifact(Dependency dep1, Dependency dep2) { + return dep1.getGroupId().equalsIgnoreCase(dep2.getGroupId()) + && dep1.getArtifactId().equalsIgnoreCase(dep2.getArtifactId()); + } + + /** + * If the visible metadata properties files are provided in the source project, add theirs' type and name filters + * to the existing visible configurations. + * + * @param sourceTypeFilters existing source type filters configured via the mojo parameter. + * @param nameFilters existing name filters configured via the mojo parameter. + */ + private void populateVisiblePropertiesFromFile(List sourceTypeFilters, List nameFilters) { + if (this.projectResourcesDir == null || !projectResourcesDir.exists()) { + return; + } + Optional optionalProperties = + loadVisiblePropertiesFromResource(FileUtils.getFile(projectResourcesDir, "META-INF", + VISIBLE_PROPERTIES_FILE_NAME)); + optionalProperties.ifPresent(properties -> { + addToFilters(properties.getProperty(CONFIGURATION_PROPERTIES_CLASSES), sourceTypeFilters); + addToFilters(properties.getProperty(CONFIGURATION_PROPERTIES_NAMES), nameFilters); + }); + if (optionalProperties.isPresent()) { + return; + } + + loadVisiblePropertiesFromResource(FileUtils.getFile( + projectResourcesDir, "META-INF", DEPRECATED_WHITELIST_FILE_NAME)) + .ifPresent(properties -> { + getLog().warn(DEPRECATED_WHITELIST_FILE_NAME + " is deprecated." + + " Please use " + VISIBLE_PROPERTIES_FILE_NAME + " instead"); + addToFilters(properties.getProperty(CONFIGURATION_PROPERTIES_CLASSES), sourceTypeFilters); + addToFilters(properties.getProperty(CONFIGURATION_PROPERTIES_NAMES), nameFilters); + }); + } + + private Optional loadVisiblePropertiesFromResource(File visiblePropertiesFile) { + if (visiblePropertiesFile.exists()) { + Properties properties = new Properties(); + try (InputStream is = new FileInputStream(visiblePropertiesFile)) { + properties.load(is); + + } + catch (Exception e) { + return Optional.empty(); + } + return Optional.of(properties); + } + return Optional.empty(); + } + + private void addToFilters(String csvFilterProperties, List filterList) { + if (!StringUtils.isEmpty(csvFilterProperties)) { + for (String filterProperty : csvFilterProperties.trim().split(",")) { + if (StringUtils.hasText(filterProperty)) { + if (!filterList.contains(filterProperty.trim())) { + filterList.add(filterProperty.trim()); + } + } + } + } + } + + /** + * Section that allow to define Application and Binder configurations common across multiple applications + * and binders. + */ + public static class Global { + + private final Application application = new Application(); + + private final Map binders = new HashMap<>(); + + public Application getApplication() { + return application; + } + + public Map getBinders() { + return binders; + } + } + + /** + * Allow defining the target application configuration, excluding the Binder specific configurations. + */ + public static class Application { + + /** + * Unique name of the application for the given type. + * The generated binder specific, applications have names that combine this application name, type as well + * as the name of the binder they are produced for. + */ + private String name; + + /** + * Application type. Cloud be of type (source, processor, sink). + */ + private AppDefinition.AppType type; + + /** + * Version to be used for the generated application. + */ + private String version; + + /** + * The Spring Boot version the generated application is configured to inherit from. + */ + private String bootVersion; + + /** + * Spring configuration class used to instantiate the application. + */ + private String configClass; + + /** + * Group Id to use with the application. + */ + private String groupId = "org.springframework.cloud.stream.app"; + + /** + * The Spring Cloud Function definition. Could be a composition definition as well. + */ + private String functionDefinition; + + /** + * Custom application properties to contribute to the generated application.properties file. + */ + private Map properties = new HashMap<>(); + + /** + * Parameters used to tune the Application metadata generation. + */ + private final Metadata metadata = new Metadata(); + + private final ContainerImage containerImage = new ContainerImage(); + + private final Maven maven = new Maven(); + + private String bootPluginConfiguration; + + public String getBootPluginConfiguration() { + return bootPluginConfiguration; + } + + public void setBootPluginConfiguration(String bootPluginConfiguration) { + this.bootPluginConfiguration = bootPluginConfiguration; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public Maven getMaven() { + return maven; + } + + public Metadata getMetadata() { + return metadata; + } + + public ContainerImage getContainerImage() { + return containerImage; + } + + public String getBootVersion() { + return bootVersion; + } + + public void setBootVersion(String bootVersion) { + this.bootVersion = bootVersion; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public AppDefinition.AppType getType() { + return type; + } + + public void setType(AppDefinition.AppType type) { + this.type = type; + } + + public String getConfigClass() { + return configClass; + } + + public void setConfigClass(String configClass) { + this.configClass = configClass; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String appGroupId) { + this.groupId = appGroupId; + } + + public String getFunctionDefinition() { + return (!StringUtils.isEmpty(this.functionDefinition)) ? this.functionDefinition : + this.name + this.functionType(); + } + + public void setFunctionDefinition(String functionDefinition) { + this.functionDefinition = functionDefinition; + } + + private String functionType() { + switch (this.type) { + case processor: + return "Function"; + case sink: + return "Consumer"; + case source: + return "Supplier"; + } + throw new IllegalArgumentException("Unknown App type:" + this.type); + } + + /** + * Parameters used to tune the Application metadata generation. It allows to set a type or name based filters + * that filer-in the subset all application configuration metadata. In addition to the explicitly specified + * filters, the Mojo automatically populates the filters with values found in the + * 'dataflow-configuration-metadata-whitelist.properties" or 'dataflow-configuration-metadata.properties' files. + * + * You can also specify the version of the metadata generation plugin. + */ + public static class Metadata { + + /** + * list of source types from application's configuration metadata to include in the visible metadata. + */ + private List sourceTypeFilters = new ArrayList<>(); + + /** + * list of property names from application's configuration metadata to include in the visible metadata. + */ + private List nameFilters = new ArrayList<>(); + + /** + * Version of the metadata maven plugin to be used. + */ + private String mavenPluginVersion; + + public List getSourceTypeFilters() { + return sourceTypeFilters; + } + + public void setSourceTypeFilters(List sourceTypeFilters) { + this.sourceTypeFilters = sourceTypeFilters; + } + + public List getNameFilters() { + return nameFilters; + } + + public void setNameFilters(List nameFilters) { + this.nameFilters = nameFilters; + } + + public String getMavenPluginVersion() { + return mavenPluginVersion; + } + + public void setMavenPluginVersion(String mavenPluginVersion) { + this.mavenPluginVersion = mavenPluginVersion; + } + } + + /** + * Configuration for injection the application's visible, configuration metadata into the Container image. + */ + public static class ContainerImage { + + /** + * Target container image. Docker and OCI are supported. If not specified the Docker is selected. + */ + private AppDefinition.ContainerImageFormat format = null; + + /** + * Target container image organization name. + */ + private String orgName; + + /** + * Base images to be used by the target container image. + */ + private String baseImage; + + /** + * Enable or disable the inclusion of application's metadata into the image's labels. + */ + private boolean enableMetadata = true; + + public AppDefinition.ContainerImageFormat getFormat() { + return format; + } + + public void setFormat(AppDefinition.ContainerImageFormat format) { + this.format = format; + } + + public String getOrgName() { + return orgName; + } + + public void setOrgName(String orgName) { + this.orgName = orgName; + } + + public boolean isEnableMetadata() { + return enableMetadata; + } + + public void setEnableMetadata(boolean enableMetadata) { + this.enableMetadata = enableMetadata; + } + + public String getBaseImage() { + return baseImage; + } + + public void setBaseImage(String baseImage) { + this.baseImage = baseImage; + } + } + } + + /** + * Binder specific properties to be applied in addition to the {@link Application} parameters. + */ + public static class Binder { + + /** + * Application properties specific for the binder. Contributed to the generated application.properties file. + */ + private List properties = new ArrayList<>(); + + /** + * Binder specific maven configurations. Applied in addition to the {@link Application}'s maven configurations. + */ + private final Maven maven = new Maven(); + + + public Maven getMaven() { + return maven; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + } + + /** + * Maven configuration used inside the {@link Application} and {@link Binder} configurations. + */ + public static class Maven { + + /** + * Custom maven properties. Contributed to the generated POM's properties section. + */ + private Map properties = new HashMap<>(); + + /** + * Custom maven management dependencies. Contributed to the generated POM's dependencyManagement section. + */ + private List dependencyManagement = new ArrayList<>(); + + /** + * Custom maven dependencies. Contributed to the generated POM's dependencies section. + */ + private List dependencies = new ArrayList<>(); + + /** + * Custom maven plugins. Contributed to the generated POM's plugins section. + * + * Note: if the plugin definition uses a configuration block then the content must be wrapped within a + *
@code{
+		 * 
+		 * }
section! Find more: {@link MavenXmlWriter#toXml(Plugin)}. + */ + private List plugins = new ArrayList<>(); + + /** + * Custom maven repositories. Contributed to the generated POM's repositories section. + */ + private List repositories = new ArrayList<>(); + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public List getDependencyManagement() { + return dependencyManagement; + } + + public void setDependencyManagement(List dependencyManagement) { + this.dependencyManagement = dependencyManagement; + } + + public List getDependencies() { + return dependencies; + } + + public void setDependencies(List dependencies) { + this.dependencies = dependencies; + } + + public List getPlugins() { + return plugins; + } + + public void setPlugins(List plugins) { + this.plugins = plugins; + } + + public List getRepositories() { + return repositories; + } + + public void setRepositories(List repositories) { + this.repositories = repositories; + } + } + +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/AppDefinition.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/AppDefinition.java new file mode 100644 index 00000000..ae33ec9d --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/AppDefinition.java @@ -0,0 +1,308 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin.generator; + +import java.util.ArrayList; +import java.util.List; + +/** + * Application configurations used to parameterize the project templates. + * + * @author Christian Tzolov + * @author Soby Chacko + */ +public class AppDefinition { + + /** + * Marker 'configClass' value that indicates that this is an autoconfiguration and the @Import statement should be + * dropped. + */ + public static final String AUTOCONFIGURATION_MARKER = "AUTOCONFIGURATION"; + + /** + * Application name. + */ + private String name; + + /** + * Application type. + */ + private AppType type; + + /** + * Application version. + */ + private String version; + + /** + * Spring Boot version used as a parent by the application. + */ + private String springBootVersion; + + /** + * Application's backing Spring configuration class. + */ + private String configClass; + + /** + * Application's group ID. + */ + private String groupId; + + /** + * Spring Cloud Function definition. + */ + private String functionDefinition; + + /** + * Additional application properties. + */ + private List properties = new ArrayList<>(); + + /** + * Maven related configurations to be applied for this App definition. + */ + private final MavenDefinition maven = new MavenDefinition(); + + /** + * ContainerImage related configurations to be applied for this App definition. + */ + private final ContainerImage containerImage = new ContainerImage(); + + /** + * Application metadata related configurations to be applied for this App definition. + */ + private final Metadata metadata = new Metadata(); + + private String bootPluginConfiguration; + + public String getBootPluginConfiguration() { + return bootPluginConfiguration; + } + + public void setBootPluginConfiguration(String bootPluginConfiguration) { + this.bootPluginConfiguration = bootPluginConfiguration; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AppType getType() { + return type; + } + + public void setType(AppType type) { + this.type = type; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getSpringBootVersion() { + return springBootVersion; + } + + public void setSpringBootVersion(String springBootVersion) { + this.springBootVersion = springBootVersion; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public MavenDefinition getMaven() { + return maven; + } + + public ContainerImage getContainerImage() { + return containerImage; + } + + public Metadata getMetadata() { + return metadata; + } + + public boolean isSupplier() { + return type == AppType.source; + } + + public boolean isConsumer() { + return type == AppType.sink; + } + + public boolean isFunction() { + return type == AppType.processor; + } + + public boolean isAutoconfiguration() { + return AUTOCONFIGURATION_MARKER.equalsIgnoreCase(configClass.trim()); + } + + public String getConfigClass() { + return configClass; + } + + public void setConfigClass(String configClass) { + this.configClass = configClass; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getFunctionDefinition() { + return functionDefinition; + } + + public void setFunctionDefinition(String functionDefinition) { + this.functionDefinition = functionDefinition; + } + + public enum ContainerImageFormat { + /** + * Docker image format. + */ + Docker, + /** + * OCI format. + */ + OCI + } + + public enum AppType { + /** + * source type. + */ + source, + /** + * processor type. + */ + processor, + /** + * sink type. + */ + sink + } + + public static class ContainerImage { + /** + * Allow to generate either Docker or OCI image formats. + */ + private ContainerImageFormat format = ContainerImageFormat.Docker; + + /** + * True will attempt to inline the (white) filtered Spring Boot metadata as Base64 encoded property. + */ + private boolean enableMetadata = false; + + private String orgName = "springcloudstream"; + + private String tag = "latest"; + + private String baseImage = "springcloud/baseimage:1.0.4"; + + public ContainerImageFormat getFormat() { + return format; + } + + public void setFormat(ContainerImageFormat format) { + this.format = format; + } + + public boolean isEnableMetadata() { + return enableMetadata; + } + + public void setEnableMetadata(boolean enableMetadata) { + this.enableMetadata = enableMetadata; + } + + public String getOrgName() { + return orgName; + } + + public void setOrgName(String orgName) { + this.orgName = orgName; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getBaseImage() { + return baseImage; + } + + public void setBaseImage(String baseImage) { + this.baseImage = baseImage; + } + } + + public static class Metadata { + private List sourceTypeFilters = new ArrayList<>(); + + private List nameFilters = new ArrayList<>(); + + private String mavenPluginVersion; + + public List getSourceTypeFilters() { + return sourceTypeFilters; + } + + public void setSourceTypeFilters(List sourceTypeFilters) { + this.sourceTypeFilters = sourceTypeFilters; + } + + public List getNameFilters() { + return nameFilters; + } + + public void setNameFilters(List nameFilters) { + this.nameFilters = nameFilters; + } + + public String getMavenPluginVersion() { + return mavenPluginVersion; + } + + public void setMavenPluginVersion(String mavenPluginVersion) { + this.mavenPluginVersion = mavenPluginVersion; + } + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/BinderDefinition.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/BinderDefinition.java new file mode 100644 index 00000000..687df6a8 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/BinderDefinition.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin.generator; + +import java.util.ArrayList; +import java.util.List; + +/** + * Binder configurations used to parameterize the project templates. + * + * @author Christian Tzolov + */ +public class BinderDefinition { + + /** + * Binder name. + */ + private String name; + + /** + * Binder specific, Maven configurations contributed to the generated poms. + */ + private final MavenDefinition maven = new MavenDefinition(); + + /** + * Binder specific application properties contributed to the generated application.properties. + */ + private List properties = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MavenDefinition getMaven() { + return maven; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/MavenDefinition.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/MavenDefinition.java new file mode 100644 index 00000000..56d46fe2 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/MavenDefinition.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin.generator; + +import java.util.ArrayList; +import java.util.List; + +/** + * Maven configurations used by the {@link AppDefinition} and {@link BinderDefinition} to parametrize the generated POMs. + * + * @author Christian Tzolov + */ +public class MavenDefinition { + /** + * Contributes the properties to the Maven's properties section. + */ + private List properties = new ArrayList<>(); + + /** + * Contributes the dependencyManagement entries to the Maven's dependencyManagement dependencies. + */ + private List dependencyManagement = new ArrayList<>(); + + /** + * Contributes the dependency entries to the Maven's dependencies section. + */ + private List dependencies = new ArrayList<>(); + + /** + * Contributes the plugin entries to the Maven's plugins section. + */ + private List plugins = new ArrayList<>(); + + /** + * Contributes the repository entries to the Maven's repositories section. + */ + private List repositories = new ArrayList<>(); + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public List getDependencyManagement() { + return dependencyManagement; + } + + public void setDependencyManagement(List dependencyManagement) { + this.dependencyManagement = dependencyManagement; + } + + public List getDependencies() { + return dependencies; + } + + public void setDependencies(List dependencies) { + this.dependencies = dependencies; + } + + public List getPlugins() { + return plugins; + } + + public void setPlugins(List plugins) { + this.plugins = plugins; + } + + public List getRepositories() { + return repositories; + } + + public void setRepositories(List repositories) { + this.repositories = repositories; + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/ProjectGenerator.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/ProjectGenerator.java new file mode 100644 index 00000000..1ec76743 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/ProjectGenerator.java @@ -0,0 +1,237 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin.generator; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; +import org.apache.commons.io.FileUtils; + +import org.springframework.util.Assert; + +/** + * @author Christian Tzolov + */ +public final class ProjectGenerator { + + private ProjectGenerator() { + } + + public static ProjectGenerator getInstance() { + return new ProjectGenerator(); + } + + // Utils + public static File mkdirs(File dir) { + if (!dir.exists()) { + dir.mkdirs(); + } + if (!dir.isDirectory()) { + throw new IllegalStateException("Not a directory: " + dir); + } + return dir; + } + + public static void copy(String content, File file) throws IOException { + Files.copy(new ByteArrayInputStream(content.getBytes()), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + public static String toPkg(String text) { + return text.replace("-", "."); + } + + public static String capitalize(String text) { + return text.substring(0, 1).toUpperCase() + text.substring(1); + } + + public static String camelCase(String text) { + return Arrays.stream(text.split("-")).reduce("", (r, p) -> r + capitalize(p)); + } + + public static File pkgToDir(File parent, String packageName) { + String[] names = packageName.split("\\."); + File result = parent; + for (String p : names) { + result = file(result, p); + } + return result; + } + + public static File file(File parent, String child) { + return new File(parent, child); + } + + public void generate(ProjectGeneratorProperties generatorProperties) throws IOException { + + Map modulesTemplateProperties = new HashMap<>(); + // register {{#capitalize}}...{{/capitalize}} function. + modulesTemplateProperties.put("capitalize", (Mustache.Lambda) (frag, out) -> out.write(capitalize(frag.execute().trim()))); + // register {{#camelCase}}...{{/camelCase}} function. + modulesTemplateProperties.put("camelCase", (Mustache.Lambda) (frag, out) -> out.write(camelCase(frag.execute().trim()))); + + modulesTemplateProperties.put("app", generatorProperties.getAppDefinition()); + modulesTemplateProperties.put("binders", generatorProperties.getBinders()); + + // --------------------------------- + // Generate apps container POM + // --------------------------------- + File appParentDir = mkdirs(generatorProperties.getOutputFolder()); + copy(materialize("template/apps-modules-pom.xml", modulesTemplateProperties), + file(appParentDir, "pom.xml")); + // maven wrapper + copyMavenWrapper(appParentDir); + + // --------------------------------- + // Generate App projects + // --------------------------------- + Assert.notEmpty(generatorProperties.getBinders(), "At least one Binder must be provided"); + for (BinderDefinition binder : generatorProperties.getBinders()) { + generateAppProject(appParentDir, modulesTemplateProperties, generatorProperties.getAppDefinition(), + generatorProperties.getProjectResourcesDirectory(), binder); + } + } + + private void generateAppProject(File appRootDirectory, Map containerTemplateProperties, + AppDefinition appDefinition, File projectResourcesDirectory, BinderDefinition binder) throws IOException { + + String appClassName = String.format("%s%s%sApplication", + camelCase(appDefinition.getName()), + capitalize(appDefinition.getType().name()), + capitalize(binder.getName())); + + String appPackageName = String.format("%s.%s.%s.%s", + appDefinition.getGroupId(), + toPkg(appDefinition.getName()), + appDefinition.getType(), + binder.getName()); + + Map appTemplateProperties = new HashMap<>(containerTemplateProperties); + + // Shortcut substitutions. Prevent complicated expressions in the templates. + appTemplateProperties.put("app-class-name", appClassName); + appTemplateProperties.put("app-package-name", appPackageName); + appTemplateProperties.put("app-binder", binder); + + // app POM + File appDir = + mkdirs(file(appRootDirectory, appDefinition.getName() + "-" + appDefinition.getType() + "-" + binder.getName())); + + copy(materialize("template/app-pom.xml", appTemplateProperties), file(appDir, "pom.xml")); + + File appMainSrcDir = mkdirs(pkgToDir(appDir, "src.main.java." + appPackageName)); + + File appMainResourceDir = mkdirs(pkgToDir(appDir, "src.main.resources")); + + // copy the entire project's src/main/resources directory + if (projectResourcesDirectory != null && projectResourcesDirectory.exists()) { + FileUtils.copyDirectory(projectResourcesDirectory, appMainResourceDir); + } + + // application.properties + String materializedAppProperties = materialize("template/app.properties", appTemplateProperties); + // Note: the application properties file may already exist from the parent's project src/main/resources dir. + File appPropertyFile = file(appMainResourceDir, "application.properties"); + //FileUtils.writeStringToFile(appPropertyFile, materializedAppProperties, "UTF8", true); + Properties appProps = new Properties(); + if (appPropertyFile.exists()) { + appProps.load(new FileInputStream(appPropertyFile)); + } + Properties genAppProps = new Properties(); + genAppProps.load(new ByteArrayInputStream(materializedAppProperties.getBytes(StandardCharsets.UTF_8))); + + // Generated properties override the inherited! + appProps.putAll(genAppProps); + appProps.store(new FileWriter(appPropertyFile), "App generator properties"); + + copy(materialize("template/App.java", appTemplateProperties), + file(appMainSrcDir, appClassName + ".java")); + + // TESTS + File appTestSrcDir = mkdirs(pkgToDir(appDir, "src.test.java." + appPackageName)); + + copy(materialize("template/AppTests.java", appTemplateProperties), + file(appTestSrcDir, appClassName + "Tests.java")); + + // README + copy(materialize("template/README.adoc", appTemplateProperties), + file(appDir, "README.adoc")); + + // maven wrapper + copyMavenWrapper(appDir); + } + + private void copyMavenWrapper(File appDir) throws IOException { + + // mvnw + copyResource("template/mvnw", file(appDir, "mvnw")); + file(appDir, "mvnw").setExecutable(true); + + copyResource("template/mvnw.cmd", file(appDir, "mvnw.cmd")); + + File dotMavenDir = mkdirs(new File(appDir, ".mvn")); + + // .mvn/jvm.config + copyResource("template/.mvn/jvm.config", file(dotMavenDir, "jvm.config")); + + // .mvn/maven.config + copyResource("template/.mvn/maven.config", file(dotMavenDir, "maven.config")); + + File dotMavenWrapper = mkdirs(file(appDir, ".mvn/wrapper")); + + // .mvn/wrapper/maven-wrapper.jar + copyResource("template/.mvn/wrapper/maven-wrapper.jar", + file(dotMavenWrapper, "maven-wrapper.jar")); + + // .mvn/wrapper/maven-wrapper.properties + copyResource("template/.mvn/wrapper/maven-wrapper.properties", + file(dotMavenWrapper, "maven-wrapper.properties")); + + // .mvn/wrapper/MavenWrapperDownloader.java + copyResource("template/.mvn/wrapper/MavenWrapperDownloader.java", + file(dotMavenWrapper, "MavenWrapperDownloader.java")); + } + + private String materialize(String templatePath, Map templateProperties) throws IOException { + try (InputStreamReader resourcesTemplateReader = new InputStreamReader( + Objects.requireNonNull(this.getClass().getClassLoader().getResourceAsStream(templatePath)))) { + Template resourceTemplate = Mustache.compiler().escapeHTML(false).compile(resourcesTemplateReader); + return resourceTemplate.execute(templateProperties); + } + } + + private void copyResource(String resourcePath, File toFile) throws IOException { + try (InputStream resourcesStream = Objects.requireNonNull( + this.getClass().getClassLoader().getResourceAsStream(resourcePath))) { + Files.copy(resourcesStream, toFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/ProjectGeneratorProperties.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/ProjectGeneratorProperties.java new file mode 100644 index 00000000..d2a9eb07 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/generator/ProjectGeneratorProperties.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin.generator; + +import java.io.File; +import java.util.List; + +/** + * @author Christian Tzolov + */ +public class ProjectGeneratorProperties { + + /** + * Source, processor and sink application configuration. + */ + private AppDefinition appDefinition; + + /** + * Location where the project source files are written. + */ + private File outputFolder = new File("./target/output"); + + /** + * List of binders to generate applications for. + */ + private List binders; + + /** + * The location of a project's src/main/resources directory. + */ + private File projectResourcesDirectory; + + public File getOutputFolder() { + return outputFolder; + } + + public void setOutputFolder(File outputFolder) { + this.outputFolder = outputFolder; + } + + public AppDefinition getAppDefinition() { + return appDefinition; + } + + public void setAppDefinition(AppDefinition appDefinition) { + this.appDefinition = appDefinition; + } + + public List getBinders() { + return binders; + } + + public void setBinderDefinitions(List binders) { + this.binders = binders; + } + + public File getProjectResourcesDirectory() { + return projectResourcesDirectory; + } + + public void setProjectResourcesDirectory(File projectResourcesDirectory) { + this.projectResourcesDirectory = projectResourcesDirectory; + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/doc/MojoConfiguration.jpg b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/doc/MojoConfiguration.jpg new file mode 100644 index 00000000..2df425ea Binary files /dev/null and b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/doc/MojoConfiguration.jpg differ diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/jvm.config b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/jvm.config new file mode 100644 index 00000000..0e7dabef --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom \ No newline at end of file diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/maven.config b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/maven.config new file mode 100644 index 00000000..3b8cf46e --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/maven.config @@ -0,0 +1 @@ +-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/MavenWrapperDownloader.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100755 index 00000000..2e394d5b --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,110 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +*/ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * 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/0.4.2/maven-wrapper-0.4.2.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 direcrory '" + 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 { + 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(); + } + +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/maven-wrapper.jar new file mode 100755 index 00000000..01e67997 Binary files /dev/null and b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 00000000..71793467 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/App.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/App.java new file mode 100644 index 00000000..efe8698a --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/App.java @@ -0,0 +1,33 @@ +/* + * 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 {{app-package-name}}; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + + +@SpringBootApplication +{{^app.autoconfiguration}} +@Import({ {{app.configClass}} }) +{{/app.autoconfiguration}} +public class {{app-class-name}} { + + public static void main(String[] args) { + SpringApplication.run({{app-class-name}}.class, args); + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/AppTests.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/AppTests.java new file mode 100644 index 00000000..c10aa855 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/AppTests.java @@ -0,0 +1,29 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package {{app-package-name}}; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class {{app-class-name}}Tests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/README.adoc b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/README.adoc new file mode 100644 index 00000000..8bfbe8b6 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/README.adoc @@ -0,0 +1 @@ += Spring Cloud Stream {{#camelCase}}{{app.name}}{{/camelCase}} {{#capitalize}}{{app.type}}{{/capitalize}} {{#capitalize}}{{app-binder.name}}{{/capitalize}} Binder Application diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/app-pom.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/app-pom.xml new file mode 100644 index 00000000..eb81984c --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/app-pom.xml @@ -0,0 +1,309 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + {{app.springBootVersion}} + + + {{app.groupId}} + {{app.name}}-{{app.type}}-{{app-binder.name}} + {{app.version}} + {{app.name}}-{{app.type}}-{{app-binder.name}} + Spring Cloud Stream {{#camelCase}}{{app.name}}{{/camelCase}} {{#capitalize}}{{app.type}}{{/capitalize}} {{#capitalize}}{{app-binder.name}}{{/capitalize}} Binder Application + + + + + repo.spring.io + Spring Release Repository + https://repo.spring.io/libs-release-local + + + repo.spring.io + Spring Snapshot Repository + https://repo.spring.io/libs-snapshot-local + + + + + true + UTF-8 + + {{#app.maven.properties}} + {{this}} + {{/app.maven.properties}} + + {{#app-binder.maven.properties}} + {{this}} + {{/app-binder.maven.properties}} + + + + + + + {{#app-binder.maven.dependencyManagement}} + {{this}} + {{/app-binder.maven.dependencyManagement}} + + {{#app.maven.dependencyManagement}} + {{this}} + {{/app.maven.dependencyManagement}} + + + + + + + {{#app.maven.dependencies}} + {{this}} + {{/app.maven.dependencies}} + + {{#app-binder.maven.dependencies}} + {{this}} + {{/app-binder.maven.dependencies}} + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + {{#app.maven.repositories}} + {{this}} + {{/app.maven.repositories}} + + {{#app-binder.maven.repositories}} + {{this}} + {{/app-binder.maven.repositories}} + + + + + true + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + + true + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + + {{#app.maven.plugins}} + {{this}} + {{/app.maven.plugins}} + + {{#app-binder.maven.plugins}} + {{this}} + {{/app-binder.maven.plugins}} + + + org.springframework.boot + spring-boot-maven-plugin + {{#app.bootPluginConfiguration}} + + {{this}} + + {{/app.bootPluginConfiguration}} + + + + com.google.cloud.tools + jib-maven-plugin + 3.3.0 + + + {{app.containerImage.baseImage}} + + + {{app.containerImage.orgName}}/${project.artifactId}:{{app.containerImage.tag}} + + + USE_CURRENT_TIMESTAMP + {{app.containerImage.format}} + {{#app.containerImage.enableMetadata}} + + + ${org.springframework.cloud.dataflow.spring.configuration.metadata.json} + + ${project.artifactId} + ${project.version} + + {{/app.containerImage.enableMetadata}} + + + + + {{#app.containerImage.enableMetadata}} + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + process-classes + + read-project-properties + + + + + ${project.build.outputDirectory}/META-INF/spring-configuration-metadata-encoded.properties + + + + + + + {{/app.containerImage.enableMetadata}} + + + org.springframework.cloud + spring-cloud-dataflow-apps-metadata-plugin + {{app.metadata.mavenPluginVersion}} + + {{#app.containerImage.enableMetadata}} + true + + + {{#app.metadataNameFilters}} + {{this}} + {{/app.metadataNameFilters}} + + + {{#app.metadata.sourceTypeFilters}} + {{this}} + {{/app.metadata.sourceTypeFilters}} + + + {{/app.containerImage.enableMetadata}} + + + + aggregate-metadata + compile + + aggregate-metadata + + + + + + maven-surefire-plugin + 2.21.0 + + ${skipTests} + + + + maven-javadoc-plugin + + + javadoc + package + + jar + + + + + true + + + + maven-source-plugin + + + attach-sources + package + + jar + + + + + + + + + milestone + + + repo.spring.io + Spring Milestone Repository + https://repo.spring.io/libs-milestone-local + + + + + central + + + + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + sonatype-nexus-staging + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/app.properties b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/app.properties new file mode 100644 index 00000000..84c4041a --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/app.properties @@ -0,0 +1,30 @@ +spring.application.name=${vcap.application.name:{{app.name}}-{{app.type}}} +info.app.name=@project.artifactId@ +info.app.description=@project.description@ +info.app.version=@project.version@ +management.endpoints.web.exposure.include=health,info,bindings +management.endpoints.web.discovery.enabled=true +management.info.env.enabled=true +spring.cloud.function.definition={{app.functionDefinition}} + +{{#app.consumer}} +spring.cloud.stream.function.bindings.{{app.functionDefinition}}-in-0=input +{{/app.consumer}} + +{{#app.supplier}} +spring.cloud.stream.function.bindings.{{app.functionDefinition}}-out-0=output +{{/app.supplier}} + +{{#app.function}} +spring.cloud.stream.function.bindings.{{app.functionDefinition}}-in-0=input +spring.cloud.stream.function.bindings.{{app.functionDefinition}}-out-0=output +{{/app.function}} + +{{#app.properties}} +{{this}} +{{/app.properties}} + +{{#app-binder.properties}} +{{this}} +{{/app-binder.properties}} + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/apps-modules-pom.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/apps-modules-pom.xml new file mode 100644 index 00000000..aba173f8 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/apps-modules-pom.xml @@ -0,0 +1,103 @@ + + + + 4.0.0 + {{app.groupId}} + {{app.name}}-{{app.type}}-apps + {{app.version}} + pom + + Apps Modules + Parent project for generated apps project modules + http://spring.io/spring-cloud + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + 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. + + + + + schacko + Soby Chacko + schacko at pivotal.io + Pivotal Software, Inc. + http://www.spring.io + + developer + + + + tzolov + Christian Tzolov + ctzolov at pivotal.io + Pivotal Software, Inc. + http://www.spring.io + + developer + + + + + + {{#binders}} + {{app.name}}-{{app.type}}-{{this.name}} + {{/binders}} + + + + scm:git:git://github.com/spring-cloud/spring-cloud-stream-app-starters.git + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-stream-app-starters.git + https://github.com/spring-cloud/spring-cloud-stream-app-starters + + + + repo.spring.io + Spring Release Repository + https://repo.spring.io/libs-release-local + + + repo.spring.io + Spring Snapshot Repository + https://repo.spring.io/libs-snapshot-local + + + + + + com.google.cloud.tools + jib-maven-plugin + 3.3.0 + + + + + + milestone + + + repo.spring.io + Spring Milestone Repository + https://repo.spring.io/libs-milestone-local + + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/mvnw b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/mvnw new file mode 100755 index 00000000..c5e1a770 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/mvnw @@ -0,0 +1,254 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +echo "Running version check" +VERSION=$( sed '\!//' -e 's!.*$!!' ) +echo "The found version is [${VERSION}]" + +if echo $VERSION | egrep -q 'M|RC'; then + echo Activating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" +else + if echo $VERSION | egrep -q 'SNAPSHOT'; then + echo Deactivating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') + echo Deactivating \"central\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q central && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pcentral//') + else + echo Deactivating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') + echo Activating \"central\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pcentral" + fi +fi + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/mvnw.cmd b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/mvnw.cmd new file mode 100644 index 00000000..eb9a292a --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/template/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/templates/assembly.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/templates/assembly.xml new file mode 100644 index 00000000..690a8327 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/main/resources/templates/assembly.xml @@ -0,0 +1,15 @@ + + ${artifactId} + + + + ${groupId}:${artifactId} + + . + ${artifactId}.jar + + + \ No newline at end of file diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/MojoHarnessTest.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/MojoHarnessTest.java new file mode 100644 index 00000000..2c49ef66 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/MojoHarnessTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.plugin.testing.MojoRule; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + */ +public class MojoHarnessTest { + + @Rule + public TemporaryFolder projectHome = new TemporaryFolder(); + + @Rule + public MojoRule mojoRule = new MojoRule(); + + @Test + public void testSomething() throws Exception { + + File pomRoot = new File("src/test/resources/unit/http-source-apps/"); + + SpringCloudStreamAppGeneratorMojo myMojo = (SpringCloudStreamAppGeneratorMojo) + mojoRule.lookupConfiguredMojo(pomRoot, "generate-app"); + + assertThat(myMojo).isNotNull(); + + myMojo.execute(); + + assertThat(new File("./target/apps/http-source-kafka/src/main/resources/test.txt")).exists(); + + assertThat(new File("./target/apps/http-source-kafka/src/main/resources/application.properties")).exists(); + + Properties applicationProperties = new Properties(); + applicationProperties.load(new FileReader("./target/apps/http-source-kafka/src/main/resources/application.properties")); + assertThat(applicationProperties.getProperty("spring.cloud.function.definition")).isEqualTo("httpSupplier"); + + assertThat(applicationProperties.getProperty("static.property")).isEqualTo("bla"); + + Model pomModel = getModel(new File("./target/apps")); + + List dependencies = pomModel.getDependencies(); + assertThat(dependencies.size()).isEqualTo(15); + + assertThat(dependencies.stream() + .filter(d -> d.getArtifactId().equals("http-supplier")).count()).isEqualTo(1); + + assertThat(dependencies.stream() + .filter(d -> d.getArtifactId().equals("stream-applications-postprocessor-common")).count()).isEqualTo(1); + + assertThat(dependencies.stream() + .filter(d -> d.getArtifactId().equals("spring-cloud-stream-binder-kafka")).count()).isEqualTo(1); + + Parent parent = pomModel.getParent(); + assertThat(parent.getArtifactId()).isEqualTo("spring-boot-starter-parent"); + assertThat(parent.getVersion()).isEqualTo("2.3.0.M1"); + + assertThat(pomModel.getArtifactId()).isEqualTo("http-source-kafka"); + assertThat(pomModel.getGroupId()).isEqualTo("org.springframework.cloud.stream.app.test"); + assertThat(pomModel.getName()).isEqualTo("http-source-kafka"); + assertThat(pomModel.getVersion()).isEqualTo("3.0.0.BUILD-SNAPSHOT"); + assertThat(pomModel.getDescription()).isEqualTo("Spring Cloud Stream Http Source Kafka Binder Application"); + + List plugins = pomModel.getBuild().getPlugins(); + + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("spring-boot-maven-plugin")).count()).isEqualTo(1); + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("properties-maven-plugin")).count()).isEqualTo(1); + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("jib-maven-plugin")).count()).isEqualTo(1); + + Plugin jibPlugin = plugins.stream().filter(p -> p.getArtifactId().equals("jib-maven-plugin")).findFirst().get(); + assertThat(jibPlugin.getConfiguration().toString()) + .contains("" + + "${org.springframework.cloud.dataflow.spring.configuration.metadata.json}" + + ""); + assertThat(jibPlugin.getConfiguration().toString()).contains("testspringcloud/${project.artifactId}:3.0.0.BUILD-SNAPSHOT"); + assertThat(jibPlugin.getConfiguration().toString()).contains("globalBaseImage"); + assertThat(pomModel.getRepositories().size()).isEqualTo(5); + + assertThat(pomModel.getRepositories().stream().map(r -> r.getId()).collect(Collectors.toList())) + .contains("bintray-global", "bintray-application", "bintray-binder"); + } + + private Model getModel(File rootPath) { + File pomXml = new File(new File(rootPath, "http-source-kafka"), "pom.xml"); + try (InputStream is = new FileInputStream(pomXml)) { + return new MavenXpp3Reader().read(is); + } + catch (IOException | XmlPullParserException e) { + throw new IllegalStateException(e); + } + } +} + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/SpringCloudStreamAppGeneratorMojoTest.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/SpringCloudStreamAppGeneratorMojoTest.java new file mode 100644 index 00000000..e06347c9 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/SpringCloudStreamAppGeneratorMojoTest.java @@ -0,0 +1,229 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.dataflow.app.plugin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.springframework.cloud.dataflow.app.plugin.generator.AppDefinition; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author Christian Tzolov + * @author Soby Chacko + */ +public class SpringCloudStreamAppGeneratorMojoTest { + + @Rule + public TemporaryFolder projectHome = new TemporaryFolder(); + + private SpringCloudStreamAppGeneratorMojo springCloudStreamAppMojo = new SpringCloudStreamAppGeneratorMojo(); + + private Class mojoClazz = springCloudStreamAppMojo.getClass(); + + private SpringCloudStreamAppGeneratorMojo.Application application; + + @Before + public void before() throws NoSuchFieldException { + + application = new SpringCloudStreamAppGeneratorMojo.Application(); + application.setName("log"); + application.setType(AppDefinition.AppType.sink); + application.setVersion("3.0.0.BUILD-SNAPSHOT"); + application.setConfigClass("io.pivotal.java.function.log.consumer.LogConsumerConfiguration.class"); + + application.getContainerImage().setFormat(AppDefinition.ContainerImageFormat.Docker); + application.getContainerImage().setBaseImage("base/image"); + + application.getMetadata().getSourceTypeFilters().add("io.pivotal.java.function.log.consumer.LogConsumerProperties"); + application.getMetadata().getNameFilters().add("server.port"); + + application.getProperties().put("spring.cloud.streamapp.security.enabled", "false"); + application.getProperties().put("spring.cloud.streamapp.security.csrf-enabled", "false"); + + Dependency dep = new Dependency(); + dep.setGroupId("io.pivotal.java.function"); + dep.setArtifactId("log-consumer"); + dep.setVersion("1.0.0.BUILD-SNAPSHOT"); + + application.getMaven().getDependencies().add(dep); + + // BOM + application.setBootVersion("2.3.0.M1"); + application.getMetadata().setMavenPluginVersion("1.0.2.BUILD-SNAPSHOT"); + + setMojoProperty("application", application); + + //Binders + SpringCloudStreamAppGeneratorMojo.Binder kafkaBinder = new SpringCloudStreamAppGeneratorMojo.Binder(); + Dependency kafkaDep = new Dependency(); + kafkaDep.setGroupId("org.springframework.cloud"); + kafkaDep.setArtifactId("spring-cloud-stream-binder-kafka"); + kafkaBinder.getMaven().getDependencies().add(kafkaDep); + + SpringCloudStreamAppGeneratorMojo.Binder rabbitBinder = new SpringCloudStreamAppGeneratorMojo.Binder(); + Dependency rabbitDep = new Dependency(); + rabbitDep.setGroupId("org.springframework.cloud"); + rabbitDep.setArtifactId("spring-cloud-stream-binder-rabbit"); + rabbitBinder.getMaven().getDependencies().add(rabbitDep); + + Map binders = new HashMap<>(); + binders.put("kafka", kafkaBinder); + binders.put("rabbit", rabbitBinder); + + setMojoProperty("binders", binders); + + //setMojoProperty("generatedProjectHome", "./target/apps"); + setMojoProperty("generatedProjectHome", projectHome.getRoot().getAbsolutePath()); + } + + @Test + public void testWithDisabledContainerMetadata() throws Exception { + + // disable metadata in container image (Default) + application.getContainerImage().setEnableMetadata(false); + + springCloudStreamAppMojo.execute(); + + //Model pomModel = getModel(new File("./target/apps")); + Model pomModel = getModel(new File(projectHome.getRoot().getAbsolutePath())); + List plugins = pomModel.getBuild().getPlugins(); + + // The properties-maven-plugin should not be defined if the container metadata is not enabled. + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("properties-maven-plugin")).count()).isEqualTo(0); + + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("jib-maven-plugin")).count()).isEqualTo(1); + + Plugin jibPlugin = plugins.stream().filter(p -> p.getArtifactId().equals("jib-maven-plugin")).findFirst().get(); + assertThat(jibPlugin.getConfiguration().toString()) + .doesNotContain("" + + "${org.springframework.cloud.dataflow.spring.configuration.metadata.json}" + + ""); + } + + @Test + public void testDefaultProjectCreationByPlugin() throws Exception { + + // Enable Metadata in Container Image! + application.getContainerImage().setEnableMetadata(true); + + springCloudStreamAppMojo.execute(); + + //assertGeneratedPomXml(new File("./target/apps")); + assertGeneratedPomXml(new File(projectHome.getRoot().getAbsolutePath())); + } + + @Test + public void testCustomBootMavenPluginConfiguration() throws Exception { + application.setBootPluginConfiguration("\n" + + " \n" + + " org.python\n" + + " jython-standalone\n" + + " \n" + + " \n" + + " ]]>"); + + springCloudStreamAppMojo.execute(); + + Model pomModel = getModel(new File(projectHome.getRoot().getAbsolutePath())); + List plugins = pomModel.getBuild().getPlugins(); + final Optional bootPlugin = plugins.stream().filter(p -> p.getArtifactId().equals("spring-boot-maven-plugin")).findFirst(); + assertThat(bootPlugin.isPresent()).isTrue(); + final Plugin plugin = bootPlugin.get(); + final Xpp3Dom configuration = (Xpp3Dom) plugin.getConfiguration(); + assertThat(configuration.getValue().contains("")).isTrue(); + assertThat(configuration.getValue().contains("jython-standalone")).isTrue(); + assertThat(configuration.getValue().contains("")).isTrue(); + } + + private void assertGeneratedPomXml(File rootPath) { + + Model pomModel = getModel(rootPath); + + List dependencies = pomModel.getDependencies(); + assertThat(dependencies.size()).isEqualTo(3); + + assertThat(dependencies.stream() + .filter(d -> d.getArtifactId().equals("log-consumer")).count()).isEqualTo(1); + + assertThat(dependencies.stream() + .filter(d -> d.getArtifactId().equals("spring-cloud-stream-binder-kafka")).count()).isEqualTo(1); + + Parent parent = pomModel.getParent(); + assertThat(parent.getArtifactId()).isEqualTo("spring-boot-starter-parent"); + assertThat(parent.getVersion()).isEqualTo("2.3.0.M1"); + + assertThat(pomModel.getArtifactId()).isEqualTo("log-sink-kafka"); + assertThat(pomModel.getGroupId()).isEqualTo("org.springframework.cloud.stream.app"); + assertThat(pomModel.getName()).isEqualTo("log-sink-kafka"); + assertThat(pomModel.getVersion()).isEqualTo("3.0.0.BUILD-SNAPSHOT"); + assertThat(pomModel.getDescription()).isEqualTo("Spring Cloud Stream Log Sink Kafka Binder Application"); + + List plugins = pomModel.getBuild().getPlugins(); + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("spring-boot-maven-plugin")).count()).isEqualTo(1); + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("properties-maven-plugin")).count()).isEqualTo(1); + assertThat(plugins.stream().filter(p -> p.getArtifactId().equals("jib-maven-plugin")).count()).isEqualTo(1); + + Plugin jibPlugin = plugins.stream().filter(p -> p.getArtifactId().equals("jib-maven-plugin")).findFirst().get(); + assertThat(jibPlugin.getConfiguration().toString()) + .contains("" + + "${org.springframework.cloud.dataflow.spring.configuration.metadata.json}" + + ""); + assertThat(jibPlugin.getConfiguration().toString()).contains("base/image"); + + assertThat(pomModel.getRepositories().size()).isEqualTo(2); + } + + private Model getModel(File rootPath) { + File pomXml = new File(new File(rootPath, "log-sink-kafka"), "pom.xml"); + try (InputStream is = new FileInputStream(pomXml)) { + return new MavenXpp3Reader().read(is); + } + catch (IOException | XmlPullParserException e) { + throw new IllegalStateException(e); + } + } + + private void setMojoProperty(String propertyName, Object value) throws NoSuchFieldException { + Field mojoProperty = mojoClazz.getDeclaredField(propertyName); + mojoProperty.setAccessible(true); + ReflectionUtils.setField(mojoProperty, springCloudStreamAppMojo, value); + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/pom.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/pom.xml new file mode 100644 index 00000000..d65f0904 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/pom.xml @@ -0,0 +1,302 @@ + + + 4.0.0 + + org.springframework.cloud.stream.app + http-source-apps + 3.0.0.BUILD-SNAPSHOT + http-source-apps + http source apps + jar + + + 2.3.0.M1 + 3.0.0.BUILD-SNAPSHOT + 2.1.1.RELEASE + 1.0.2.BUILD-SNAPSHOT + + + 1.0.0.BUILD-SNAPSHOT + Hoxton.RELEASE + Horsham.SR2 + 3.0.2.RELEASE + springcloud/baseimage:1.0.4 + + + + + + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + 1.0.0-SNAPSHOT + + + app-gen + package + + generate-app + + + + + + + ${spring-boot.version} + + 2.0.2.BUILD-SNAPSHOT + + + globalOrgName + globalBaseImage + true + + + value1 + value2 + + + + value1 + value2 + + + + org.springframework.cloud + spring-cloud-stream-dependencies + ${spring-cloud-stream-dependencies.version} + + + org.springframework.cloud + spring-cloud-function-dependencies + ${spring-cloud-function-dependencies.version} + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + + + + + org.springframework.cloud.stream.app + stream-apps-security-common + ${stream-apps-core.version} + + + org.springframework.cloud.stream.app + stream-apps-micrometer-common + ${stream-apps-core.version} + + + org.springframework.cloud.stream.app + stream-applications-postprocessor-common + 3.0.0-SNAPSHOT + + + io.micrometer + micrometer-registry-influx + + + io.micrometer + micrometer-registry-prometheus + + + io.micrometer + micrometer-registry-datadog + + + io.pivotal.cfenv + java-cfenv-boot + ${java-cfenv-boot.version} + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-security + + + + + org.logaritex + test-additional-maven-plugin + 3.0.0 + + + ${apps.base-image} + + + springcloudstream:${project.artifactId}:3.0.0.BUILD-SNAPSHOT + + + USE_CURRENT_TIMESTAMP + Docker + + ${org.springframework.cloud.dataflow.spring.configuration.metadata.json} + + + ]]> + + + + sign-artifacts + verify + + sign + + + + + + + + + false + + bintray-global + bintray-global + https://dl.bintray.com/globbals/maven + + + + + + + + + + + org.springframework.cloud + spring-cloud-stream-binder-kafka + + + + + + false + + bintray-binder + bintray-binder + https://dl.bintray.com/binder/maven + + + + + + + + + org.springframework.cloud + spring-cloud-stream-binder-rabbit + + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + 1.0.0-SNAPSHOT + + + ./target/apps + + http + source + ${project.version} + io.pivotal.java.function.http.supplier.HttpSupplierConfiguration.class + org.springframework.cloud.stream.app.test + + + reactive + false + false + + value2Updated + + + + testspringcloud + true + + + + + value1 + + + + io.pivotal.java.function + http-supplier + ${java-functions.version} + + + + + + false + + bintray-application + bintray-app + https://dl.bintray.com/app/maven + + + + + + + io.pivotal.java.function.http.supplier.HttpSourceProperties + io.pivotal.java.function.http.supplier.HttpSourceProperties$Cors + + + server.port + + + + + + + org.springframework.cloud + spring-cloud-app-starter-metadata-maven-plugin + 2.0.0.BUILD-SNAPSHOT + + true + + + + server.port + + + io.pivotal.java.function.http.supplier.HttpSourceProperties + io.pivotal.java.function.http.supplier.HttpSourceProperties$Cors + + + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/src/main/resources/application.properties b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/src/main/resources/application.properties new file mode 100644 index 00000000..42ca5767 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/src/main/resources/application.properties @@ -0,0 +1 @@ +static.property=bla diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/src/main/resources/test.txt b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/src/main/resources/test.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-generator-plugin/src/test/resources/unit/http-source-apps/src/main/resources/test.txt @@ -0,0 +1 @@ +test diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/README.adoc b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/README.adoc new file mode 100644 index 00000000..4920beb5 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/README.adoc @@ -0,0 +1,95 @@ += Spring Cloud Stream & Task Maven Metadata Plugin + +Maven plugin for aggregating Spring Boot metadata from all dependencies of a project into +a single metadata-only artifact. + +== Usage + +To use this plugin, configure this plugin for your app project (either directly or through a parent POM): +``` + + + + org.springframework.cloud.stream.app.plugin + spring-cloud-app-starter-metadata-maven-plugin + 2.0.0.BUILD-SNAPSHOT + + + aggregate-metadata + compile + + aggregate-metadata + + + + + + +``` + +This will produce and attach to the build a jar file named `--metadata.jar` (`metadata` being the +maven classifier used for the artifact, which can be customized as such:) +``` + + + + org.springframework.cloud.stream.app.plugin + spring-cloud-app-starter-metadata-maven-plugin + 2.0.0.BUILD-SNAPSHOT + + + aggregate-metadata + compile + + aggregate-metadata + + + foobar + + + + + + +``` + +You can filter in subset of the gathered metadata and store it into a java property file: `META-INF/spring-configuration-metadata-encoded.properties`. +This file has single property `org.springframework.cloud.dataflow.spring.configuration.metadata.json` which contains the pre-filtered metadata encoded as Base64. +To activate this feature you need to set the `storeFilteredMetadata` parameter to `true`. Use the `metadataFilter` to configure the whitelisted metadata content to include. +For example: +``` + + + + org.springframework.cloud + spring-cloud-app-starter-metadata-maven-plugin + 2.0.0.BUILD-SNAPSHOT + + + true + + + + server.port + + + io.pivotal.java.function.http.supplier.HttpSourceProperties + io.pivotal.java.function.http.supplier.HttpSourceProperties$Cors + + + + + + + aggregate-metadata + compile + + aggregate-metadata + + + + + + +``` + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/pom.xml b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/pom.xml new file mode 100644 index 00000000..d566b116 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + + spring-cloud-dataflow-apps-metadata-plugin + maven-plugin + + + org.springframework.cloud + spring-cloud-dataflow-apps-plugin-parent + 1.0.8-SNAPSHOT + .. + + + + 3.6.3 + 4.12 + 3.11.1 + 1.8 + 5.2.8.RELEASE + 1.26 + + + + + org.apache.maven + maven-plugin-api + ${maven.version} + provided + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + org.yaml + snakeyaml + ${snakeyaml.version} + + + org.apache.maven + maven-core + ${maven.version} + + + org.springframework.boot + spring-boot-configuration-processor + 2.3.2.RELEASE + + + io.rsocket + rsocket-core + 1.0.1 + + + org.springframework.boot + spring-boot-loader + 2.3.2.RELEASE + + + junit + junit + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.4 + provided + + + org.apache.commons + commons-text + ${commons-text.version} + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + spring + + + + true + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + + true + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java new file mode 100644 index 00000000..d62faad2 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java @@ -0,0 +1,625 @@ +/* + * Copyright 2017-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.dataflow.app.plugin; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.commons.text.StringEscapeUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationprocessor.metadata.ItemHint; +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; +import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller; +import org.springframework.core.io.FileSystemResource; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import static org.springframework.boot.configurationprocessor.metadata.ItemHint.ValueHint; +import static org.springframework.boot.configurationprocessor.metadata.ItemHint.ValueProvider; + +/** + * A maven plugin that will gather all Spring Boot metadata files from all transitive dependencies and will aggregate + * them in one metadata-only artifact. + * + * @author Eric Bottard + * @author David Turanski + * @author Christian Tzolov + * @author Ilayaperumal Gopinathan + */ +@Mojo( + name = "aggregate-metadata", + requiresDependencyResolution = ResolutionScope.RUNTIME, + defaultPhase = LifecyclePhase.COMPILE +) +public class MetadataAggregationMojo extends AbstractMojo { + + static final String METADATA_PATH = "META-INF/spring-configuration-metadata.json"; + + static final String VISIBLE_PROPERTIES_PATH = "META-INF/dataflow-configuration-metadata.properties"; + + static final String DEPRECATED_WHITELIST_PATH = "META-INF/dataflow-configuration-metadata-whitelist.properties"; + + static final String DEPRECATED_BACKUP_WHITELIST_PATH = "META-INF/spring-configuration-metadata-whitelist.properties"; + + static final String CONFIGURATION_PROPERTIES_CLASSES = "configuration-properties.classes"; + + static final String CONFIGURATION_PROPERTIES_NAMES = "configuration-properties.names"; + + static final String CONFIGURATION_PROPERTIES_INBOUND_PORTS = "configuration-properties.inbound-ports"; + + static final String CONFIGURATION_PROPERTIES_OUTBOUND_PORTS = "configuration-properties.outbound-ports"; + + static final String SPRING_CLOUD_FUNCTION_DEFINITION = "spring.cloud.function.definition"; + + static final String SPRING_CLOUD_STREAM_FUNCTION_DEFINITION = "spring.cloud.stream.function.definition"; + + static final String SPRING_CLOUD_STREAM_FUNCTION_BINDINGS = "spring.cloud.stream.function.bindings"; + + static final String SPRING_CLOUD_DATAFLOW_PORT_MAPPING_PROPERTIES = "dataflow-configuration-port-mapping.properties"; + + static final String SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES = "dataflow-configuration-option-groups.properties"; + + @Parameter(defaultValue = "${project}") + private MavenProject mavenProject; + + @Parameter(defaultValue = "metadata") + private String classifier; + + @Component + private MavenProjectHelper projectHelper; + + @Parameter + private boolean storeFilteredMetadata; + + @Parameter + private MetadataFilter metadataFilter; + + private final JsonMarshaller jsonMarshaller = new JsonMarshaller(); + + public void execute() throws MojoExecutionException { + Result result = new Result(gatherConfigurationMetadata(null), gatherVisibleMetadata()); + produceArtifact(result); + + if (storeFilteredMetadata) { + getLog().debug("propertyClassFilter: " + metadataFilter); + if (metadataFilter == null) { + metadataFilter = new MetadataFilter(); + } + if (result.visible.containsKey(CONFIGURATION_PROPERTIES_CLASSES)) { + String[] sourceTypes = result.visible.getProperty(CONFIGURATION_PROPERTIES_CLASSES, "").split(","); + if (sourceTypes != null && sourceTypes.length > 0) { + if (metadataFilter.getSourceTypes() == null) { + metadataFilter.setSourceTypes(new ArrayList<>()); + } + for (String sourceType : sourceTypes) { + sourceType = sourceType.trim(); + if (!metadataFilter.getSourceTypes().contains(sourceType)) { + metadataFilter.getSourceTypes().add(sourceType); + } + } + } + } + + if (result.visible.containsKey(CONFIGURATION_PROPERTIES_NAMES)) { + String[] names = result.visible.getProperty(CONFIGURATION_PROPERTIES_NAMES, "").split(","); + if (names != null && names.length > 0) { + if (metadataFilter.getNames() == null) { + metadataFilter.setNames(new ArrayList<>()); + } + for (String name : names) { + name = name.trim(); + if (!metadataFilter.getNames().contains(name)) { + metadataFilter.getNames().add(name); + } + } + } + } + + if (result.visible.containsKey(CONFIGURATION_PROPERTIES_CLASSES)) { + String[] sourceTypes = result.visible.getProperty(CONFIGURATION_PROPERTIES_CLASSES, "").split(","); + metadataFilter.getSourceTypes().addAll(Arrays.asList(sourceTypes)); + } + + storeFilteredMetadata(); + } + //Add port mapping configuration based on the application configuration. + storeInboundOutboundPortMappingConfigurations(result.getPortMappingProperties()); + } + + /** + * Store pre-filtered and json-escaped metadata into a property file. + */ + private void storeFilteredMetadata() throws MojoExecutionException { + File projectMetaInfFolder = new File(mavenProject.getBuild().getOutputDirectory(), "META-INF"); + if (!projectMetaInfFolder.exists()) { + if (!projectMetaInfFolder.mkdir()) { + throw new MojoExecutionException("Error creating META-INF folder for port mapping file!"); + } + } + try (FileWriter fileWriter = new FileWriter( + new File(projectMetaInfFolder, "spring-configuration-metadata-encoded.properties"))) { + ConfigurationMetadata metadata = gatherConfigurationMetadata(metadataFilter); + String escapedJson = StringEscapeUtils.escapeJson(toJson(metadata)); + fileWriter.write("org.springframework.cloud.dataflow.spring.configuration.metadata.json=" + escapedJson); + } + catch (IOException e) { + throw new MojoExecutionException("Error creating file ", e); + } + } + + private void storeInboundOutboundPortMappingConfigurations(Properties properties) throws MojoExecutionException { + File projectMetaInfFolder = new File(mavenProject.getBuild().getOutputDirectory(), "META-INF"); + if (!projectMetaInfFolder.exists()) { + if (!projectMetaInfFolder.mkdir()) { + throw new MojoExecutionException("Error creating META-INF folder for port mapping file!"); + } + } + try (FileWriter fileWriter = new FileWriter( + new File(projectMetaInfFolder, SPRING_CLOUD_DATAFLOW_PORT_MAPPING_PROPERTIES))) { + properties.store(fileWriter, "Spring Cloud DataFlow Port Mapping"); + } + catch (IOException e) { + throw new MojoExecutionException("Error creating file ", e); + } + } + + private String toJson(ConfigurationMetadata metadata) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + jsonMarshaller.write(metadata, baos); + String json = baos.toString(); + + // Hack to workaround the https://github.com/mojohaus/properties-maven-plugin/issues/27 and + // https://github.com/mojohaus/properties-maven-plugin/pull/38 properties-maven-plugin issues. + json = json.replaceAll("\\$\\{", "{"); + + return json; + } + + /** + * Read all existing metadata from this project runtime dependencies and merge them in a single object. + */ + /*default*/ Properties gatherVisibleMetadata() throws MojoExecutionException { + Properties visible = new Properties(); + List inboundPorts = new ArrayList<>(); + List outboundPorts = new ArrayList<>(); + try { + for (String path : mavenProject.getRuntimeClasspathElements()) { + if (Files.isDirectory(Paths.get(path))) { + for (String visibleProperties : new String[] { VISIBLE_PROPERTIES_PATH, DEPRECATED_WHITELIST_PATH, + DEPRECATED_BACKUP_WHITELIST_PATH }) { + Optional properties; + properties = getVisibleFromFile(Paths.get(path, visibleProperties)); + if (properties.isPresent()) { + if (!visibleProperties.equals(VISIBLE_PROPERTIES_PATH)) { + getLog().warn("Use of " + visibleProperties + " is deprecated." + + " Please use " + VISIBLE_PROPERTIES_PATH); + + } + visible = properties.get(); + break; + } + } + File dir = new File(path); + for (File file : dir.listFiles()) { + Properties properties = new Properties(); + if (file.isFile() && file.canRead() && file.getName().endsWith(".properties")) { + try (InputStream is = new FileInputStream(file)) { + properties.load(is); + } + } + if (file.isFile() && file.canRead() && (file.getName().endsWith(".yaml") || file.getName() + .endsWith(".yml"))) { + YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean(); + yamlPropertiesFactoryBean.setResources(new FileSystemResource(file)); + properties = yamlPropertiesFactoryBean.getObject(); + } + if (!properties.isEmpty()) { + String functionDefinitions = null; + if (properties.containsKey(SPRING_CLOUD_FUNCTION_DEFINITION)) { + functionDefinitions = properties.getProperty(SPRING_CLOUD_FUNCTION_DEFINITION); + } + else if (properties.containsKey(SPRING_CLOUD_STREAM_FUNCTION_DEFINITION)) { + functionDefinitions = properties + .getProperty(SPRING_CLOUD_STREAM_FUNCTION_DEFINITION); + } + for (String functionDefinition : StringUtils + .delimitedListToStringArray(functionDefinitions, ";")) { + if (functionDefinition != null) { + for (Object propertyKey : properties.keySet()) { + if (((String) propertyKey).startsWith( + String.format("%s.%s-in-", SPRING_CLOUD_STREAM_FUNCTION_BINDINGS, + functionDefinition))) { + inboundPorts.add(properties.getProperty((String) propertyKey)); + } + if (((String) propertyKey).startsWith( + String.format("%s.%s-out-", SPRING_CLOUD_STREAM_FUNCTION_BINDINGS, + functionDefinition))) { + outboundPorts.add(properties.getProperty((String) propertyKey)); + } + } + } + } + } + } + } + else { + try (ZipFile zipFile = new ZipFile(new File(path))) { + ZipEntry entry; + for (String zipEntry : new String[] { VISIBLE_PROPERTIES_PATH, DEPRECATED_WHITELIST_PATH, + DEPRECATED_BACKUP_WHITELIST_PATH }) { + entry = zipFile.getEntry(zipEntry); + if (entry != null) { + if (!zipEntry.equals(VISIBLE_PROPERTIES_PATH)) { + getLog().warn("Use of " + zipEntry + " is deprecated." + + " Please use " + VISIBLE_PROPERTIES_PATH); + } + visible = getVisibleFromZipFile(visible, path, zipFile, entry); + break; + } + } + } + } + } + } + catch (Exception e) { + throw new MojoExecutionException("Exception trying to read metadata from dependencies of project", e); + } + if (!inboundPorts.isEmpty()) { + visible.put(CONFIGURATION_PROPERTIES_INBOUND_PORTS, + StringUtils.arrayToCommaDelimitedString(inboundPorts.toArray(new String[0]))); + } + if (!outboundPorts.isEmpty()) { + visible.put(CONFIGURATION_PROPERTIES_OUTBOUND_PORTS, + StringUtils.arrayToCommaDelimitedString(outboundPorts.toArray(new String[0]))); + } + return visible; + } + + /*default*/ ConfigurationMetadata gatherConfigurationMetadata(MetadataFilter metadataFilters) + throws MojoExecutionException { + ConfigurationMetadata metadata = new ConfigurationMetadata(); + try { + for (String path : mavenProject.getRuntimeClasspathElements()) { + File file = new File(path); + if (file.isDirectory()) { + File localMetadata = new File(file, METADATA_PATH); + if (localMetadata.canRead()) { + try (InputStream is = new FileInputStream(localMetadata)) { + ConfigurationMetadata depMetadata = jsonMarshaller.read(is); + depMetadata = filterMetadata(depMetadata, metadataFilters); + getLog().debug("Merging metadata from " + path); + addEnumHints(depMetadata, getClassLoader(path)); + metadata.merge(depMetadata); + } + } + } + else { + try (ZipFile zipFile = new ZipFile(file)) { + ZipEntry entry = zipFile.getEntry(METADATA_PATH); + if (entry != null) { + try (InputStream inputStream = zipFile.getInputStream(entry)) { + ConfigurationMetadata depMetadata = jsonMarshaller.read(inputStream); + depMetadata = filterMetadata(depMetadata, metadataFilters); + getLog().debug("Merging metadata from " + path); + addEnumHints(depMetadata, getClassLoader(path)); + metadata.merge(depMetadata); + } + } + } + } + + // Replace all escaped double quotes by a single one. + metadata.getItems().stream().forEach(itemMetadata -> { + if (!StringUtils.isEmpty(itemMetadata.getDescription()) && itemMetadata.getDescription() + .contains("\"")) { + itemMetadata.setDescription(itemMetadata.getDescription().replaceAll("\"", "'")); + } + }); + } + } + catch (Exception e) { + throw new MojoExecutionException("Exception trying to read metadata from dependencies of project", e); + } + return metadata; + } + + @SuppressWarnings("unchecked") + private ConfigurationMetadata filterMetadata(ConfigurationMetadata metadata, MetadataFilter metadataFilters) { + if (metadataFilters == null + || (CollectionUtils.isEmpty(metadataFilters.getNames()) && CollectionUtils + .isEmpty(metadataFilters.getSourceTypes()))) { + return metadata; // nothing to filter by so take all; + } + + List sourceTypeFilters = CollectionUtils.isEmpty(metadataFilters.getSourceTypes()) ? + Collections.EMPTY_LIST : metadataFilters.getSourceTypes(); + + List nameFilters = CollectionUtils.isEmpty(metadataFilters.getNames()) ? + Collections.EMPTY_LIST : metadataFilters.getNames(); + + ConfigurationMetadata filteredMetadata = new ConfigurationMetadata(); + List visibleNames = new ArrayList<>(); + for (ItemMetadata itemMetadata : metadata.getItems()) { + String metadataName = itemMetadata.getName(); + String metadataSourceType = itemMetadata.getSourceType(); + if (StringUtils.hasText(metadataSourceType) && sourceTypeFilters.contains(metadataSourceType.trim())) { + filteredMetadata.add(itemMetadata); + visibleNames.add(itemMetadata.getName()); + } + if (StringUtils.hasText(metadataName) && nameFilters.contains(metadataName.trim())) { + filteredMetadata.add(itemMetadata); + visibleNames.add(itemMetadata.getName()); + } + + } + + // copy the hits only for the visible metadata. + for (ItemHint itemHint : metadata.getHints()) { + if (itemHint != null && visibleNames.contains(itemHint.getName())) { + filteredMetadata.add(itemHint); + } + } + + return filteredMetadata; + } + + private Properties getVisibleFromZipFile(Properties visible, String path, ZipFile zipFile, ZipEntry entry) + throws IOException { + try (InputStream inputStream = zipFile.getInputStream(entry)) { + getLog().debug("Merging visible metadata from " + path); + visible = merge(visible, inputStream); + } + return visible; + } + + private Optional getVisibleFromFile(Path visiblePropertiesPath) throws IOException { + File localVisible = visiblePropertiesPath.toFile(); + if (localVisible.canRead()) { + Properties visible = new Properties(); + try (InputStream is = new FileInputStream(localVisible)) { + getLog().debug("!!!! Merging visible metadata from " + visiblePropertiesPath.toString()); + visible = merge(visible, is); + return Optional.of(visible); + } + } + return Optional.empty(); + } + + Properties merge(Properties visible, InputStream is) throws IOException { + Properties mergedProperties = new Properties(); + mergedProperties.load(is); + + if (!mergedProperties.containsKey(CONFIGURATION_PROPERTIES_CLASSES) && !mergedProperties + .containsKey(CONFIGURATION_PROPERTIES_NAMES)) { + getLog().warn(String.format("Visible properties does not contain any required keys: %s", + StringUtils.arrayToCommaDelimitedString(new String[] { + CONFIGURATION_PROPERTIES_CLASSES, + CONFIGURATION_PROPERTIES_NAMES + }))); + return visible; + } + + if (!CollectionUtils.isEmpty(visible)) { + mergeCommaDelimitedValue(visible, mergedProperties, CONFIGURATION_PROPERTIES_CLASSES); + mergeCommaDelimitedValue(visible, mergedProperties, CONFIGURATION_PROPERTIES_NAMES); + } + + return mergedProperties; + } + + private void mergeCommaDelimitedValue(Properties currentProperties, Properties newProperties, String key) { + if (currentProperties.containsKey(key) || newProperties.containsKey(key)) { + Collection values = StringUtils.commaDelimitedListToSet(currentProperties.getProperty(key)); + values.addAll(StringUtils.commaDelimitedListToSet(newProperties.getProperty(key))); + if (newProperties.containsKey(key)) { + getLog().debug(String.format("Merging visible property %s=%s", key, newProperties.getProperty(key))); + } + newProperties.setProperty(key, StringUtils.collectionToCommaDelimitedString(values)); + + } + } + + /** + * Create a jar file with the given metadata and "attach" it to the current maven project. + */ + /*default*/ void produceArtifact(Result result) throws MojoExecutionException { + String artifactLocation = String + .format("target/%s-%s-%s.jar", mavenProject.getArtifactId(), mavenProject.getVersion(), classifier); + File output = new File(mavenProject.getBasedir(), artifactLocation); + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(output))) { + ZipEntry entry = new ZipEntry(METADATA_PATH); + jos.putNextEntry(entry); + jsonMarshaller.write(result.metadata, jos); + + entry = new ZipEntry(VISIBLE_PROPERTIES_PATH); + jos.putNextEntry(entry); + result.visible.store(jos, "Describes visible properties for this app"); + + entry = new ZipEntry(DEPRECATED_WHITELIST_PATH); + jos.putNextEntry(entry); + result.visible.store(jos, "DEPRECATED: Describes visible properties for this app"); + + entry = new ZipEntry(DEPRECATED_BACKUP_WHITELIST_PATH); + jos.putNextEntry(entry); + result.visible.store(jos, "DEPRECATED: Describes visible properties for this app"); + + entry = new ZipEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_PORT_MAPPING_PROPERTIES); + jos.putNextEntry(entry); + + entry = new ZipEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES); + jos.putNextEntry(entry); + + result.getPortMappingProperties().store(jos, "Describes visible port mapping properties for this app"); + + getLog().info(String.format("Attaching %s to current project", output.getCanonicalPath())); + projectHelper.attachArtifact(mavenProject, output, classifier); + } + catch (IOException e) { + throw new MojoExecutionException("Error writing to file", e); + } + } + + void addEnumHints(ConfigurationMetadata configurationMetadata, ClassLoader classLoader) { + + Map> providers = new HashMap<>(); + + Map itemHints = new HashMap<>(); + + for (ItemMetadata property : configurationMetadata.getItems()) { + + if (property.isOfItemType(ItemMetadata.ItemType.PROPERTY)) { + + if (ClassUtils.isPresent(property.getType(), classLoader)) { + Class clazz = ClassUtils.resolveClassName(property.getType(), classLoader); + if (clazz.isEnum()) { + List valueHints = new ArrayList<>(); + for (Object o : clazz.getEnumConstants()) { + valueHints.add(new ValueHint(o, null)); + } + + if (!providers.containsKey(property.getType())) { + providers.put(property.getType(), new ArrayList()); + } + + //Equals is not correct for ValueProvider + + boolean found = false; + for (ValueProvider valueProvider : providers.get(property.getType())) { + if (valueProvider.getName().equals(property.getType())) { + found = true; + } + } + + if (!found) { + providers.get(property.getType()).add(new ValueProvider(property.getType(), null)); + } + + itemHints.put(property.getType(), new ItemHint(property.getName(), valueHints, + new ArrayList<>(providers.get(property.getType())))); + + } + } + } + } + if (!CollectionUtils.isEmpty(itemHints)) { + for (ItemHint itemHint : itemHints.values()) { + configurationMetadata.add(itemHint); + } + } + } + + private ClassLoader getClassLoader(String jarPath) { + ClassLoader classLoader = null; + try { + classLoader = new URLClassLoader(new URL[] { new URL("file://" + jarPath) }, + this.getClass().getClassLoader()); + } + catch (MalformedURLException e) { + // pass through + } + return classLoader; + } + + public static class MetadataFilter { + private List names; + + private List sourceTypes; + + public List getNames() { + return names; + } + + public void setNames(List names) { + this.names = names; + } + + public List getSourceTypes() { + return sourceTypes; + } + + public void setSourceTypes(List sourceTypes) { + this.sourceTypes = sourceTypes; + } + + @Override + public String toString() { + return "MetadataFilter{" + + "name=" + names + + ", sourceType=" + sourceTypes + + '}'; + } + } + + /** + * A tuple holding both configuration metadata and the whitelist properties. + * + * @author Eric Bottard + */ + /*default*/ static final class Result { + private final ConfigurationMetadata metadata; + + private final Properties visible; + + private Properties getPortMappingProperties() { + Properties portMappingProperties = new Properties(); + visible.entrySet().stream() + .filter(e -> e.getKey().equals(CONFIGURATION_PROPERTIES_OUTBOUND_PORTS) || e.getKey().equals(CONFIGURATION_PROPERTIES_INBOUND_PORTS)) + .forEach(e -> portMappingProperties.put(e.getKey(), e.getValue())); + return portMappingProperties; + } + + private Result(ConfigurationMetadata metadata, Properties visible) { + this.metadata = metadata; + this.visible = visible; + } + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/EnumHintProviderTest.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/EnumHintProviderTest.java new file mode 100644 index 00000000..4acea368 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/EnumHintProviderTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2018-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.dataflow.app.plugin; + +import org.junit.Test; + +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationprocessor.metadata.ItemHint; +import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller; +import org.springframework.cloud.dataflow.completion.Expresso; +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author David Turanski + **/ +public class EnumHintProviderTest { + private JsonMarshaller jsonMarshaller = new JsonMarshaller(); + + @Test + public void test() throws Exception { + ConfigurationMetadata configurationMetadata = jsonMarshaller.read(new ClassPathResource("META-INF/spring" + + "-configuration-metadata.json").getInputStream()); + + new MetadataAggregationMojo().addEnumHints(configurationMetadata, this.getClass().getClassLoader()); + + assertThat(configurationMetadata.getHints()).hasSize(1); + + ItemHint itemHint = configurationMetadata.getHints().get(0); + + + // equals fails on ValueHint so ... + // assertThat(new ItemHint.ValueHint("SINGLE",null)).isEqualTo(new ItemHint.ValueHint("SINGLE",null)); + + assertThat(itemHint.getValues()).hasSize(2); + + assertThat(itemHint.getValues().get(0).getValue()).isEqualTo(Expresso.SINGLE); + assertThat(itemHint.getValues().get(1).getValue()).isEqualTo(Expresso.DOUBLE); + + } +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/MergeWhitelistPropertiesTest.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/MergeWhitelistPropertiesTest.java new file mode 100644 index 00000000..1e025415 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/MergeWhitelistPropertiesTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018-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.dataflow.app.plugin; + +import java.io.IOException; +import java.util.Properties; + +import org.junit.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.dataflow.app.plugin.MetadataAggregationMojo.CONFIGURATION_PROPERTIES_CLASSES; +import static org.springframework.cloud.dataflow.app.plugin.MetadataAggregationMojo.CONFIGURATION_PROPERTIES_NAMES; + +/** + * @author David Turanski + **/ + +public class MergeWhitelistPropertiesTest { + + @Test + public void merge() throws IOException { + Properties properties1 = new Properties(); + properties1.load(new ClassPathResource("META-INF/whitelist-1.properties").getInputStream()); + + Properties properties2 = new MetadataAggregationMojo().merge(properties1, + new ClassPathResource("META-INF/whitelist-2.properties").getInputStream()); + + assertThat(properties2).containsKeys(CONFIGURATION_PROPERTIES_CLASSES, CONFIGURATION_PROPERTIES_NAMES); + } + + @Test + public void mergeReverseOrder() throws IOException { + Properties properties1 = new Properties(); + properties1.load(new ClassPathResource("META-INF/whitelist-2.properties").getInputStream()); + + Properties properties2 = new MetadataAggregationMojo().merge(properties1, + new ClassPathResource("META-INF/whitelist-1.properties").getInputStream()); + + assertThat(properties2).containsKeys(CONFIGURATION_PROPERTIES_CLASSES, CONFIGURATION_PROPERTIES_NAMES); + } + +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/completion/Expresso.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/completion/Expresso.java new file mode 100644 index 00000000..95e04f68 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/completion/Expresso.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018-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.dataflow.completion; + +/** + * @author David Turanski + **/ +public enum Expresso { + SINGLE, + DOUBLE; +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/spring-configuration-metadata.json b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 00000000..d8ff7ea1 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/spring-configuration-metadata.json @@ -0,0 +1,26 @@ +{ + "groups": [ + { + "name": "filter", + "type": "foo.bar.FilterProperties", + "sourceType": "foo.bar.FilterProperties" + } + ], + "properties": [ + { + "name": "filter.expression", + "type": "org.springframework.expression.Expression", + "description": "A predicate to evaluate", + "sourceType": "foo.bar.FilterProperties", + "defaultValue": "true" + }, + { + "name": "filter.expresso", + "type": "org.springframework.cloud.dataflow.completion.Expresso", + "description": "A property of type enum and whose name starts like 'expression'", + "sourceType": "foo.bar.FilterProperties" + } + ], + "hints": [ + ] +} diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/whitelist-1.properties b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/whitelist-1.properties new file mode 100644 index 00000000..0fbbde32 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/whitelist-1.properties @@ -0,0 +1,2 @@ +configuration-properties.classes=\ + org.springframework.cloud.stream.app.tasklaunchrequest.DataflowTaskLaunchRequestProperties diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/whitelist-2.properties b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/whitelist-2.properties new file mode 100644 index 00000000..ce4f979a --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/resources/META-INF/whitelist-2.properties @@ -0,0 +1,25 @@ +configuration-properties.classes=\ + org.springframework.cloud.stream.app.trigger.TriggerPropertiesMaxMessagesDefaultUnlimited +configuration-properties.names=\ +sftp.auto-create-local-dir,\ +sftp.delete-remote-files,\ +sftp.directories,\ +sftp.factories,\ +sftp.factory.allow-unknown-keys,\ +sftp.factory.cache-sessions,\ +sftp.factory.host,\ +sftp.factory.known-hosts-expression,\ +sftp.factory.pass-phrase,\ +sftp.factory.password,\ +sftp.factory.port,\ +sftp.factory.private-key,\ +sftp.factory.username,\ +sftp.fair,\ +sftp.filename-pattern,\ +sftp.filename-regex,\ +sftp.list-only,\ +sftp.local-dir,\ +sftp.max-fetch,\ +sftp.preserve-timestamp,\ +sftp.remote-dir,\ +sftp.remote-file-separator