diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/build.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/build.gradle new file mode 100644 index 0000000000..355744da86 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' +} + +group = "com.baeldung.gradle" +version = "1.0.0-SNAPSHOT" +sourceCompatibility = JavaVersion.VERSION_17 + +repositories { + mavenLocal() + mavenCentral() + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/eugenp/tutorials" + credentials { + username = project.USERNAME + password = project.GITHUB_TOKEN + } + } +} + +dependencies { + implementation('com.baeldung.gradle:publish-package:1.0.0-SNAPSHOT') + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.0") +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.jar b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..41d9927a4d Binary files /dev/null and b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.properties b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..070cb702f0 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew.bat b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/settings.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/settings.gradle new file mode 100644 index 0000000000..0177937493 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "multiple-repositories" diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/main/java/com/baeldung/gradle/multiplerepositories/Student.java b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/main/java/com/baeldung/gradle/multiplerepositories/Student.java new file mode 100644 index 0000000000..dcb28a3742 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/main/java/com/baeldung/gradle/multiplerepositories/Student.java @@ -0,0 +1,26 @@ +package com.baeldung.gradle.multiplerepositories; + +import com.baeldung.gradle.publishPackage.User; + +public class Student extends User { + + private String studentCode; + + private String lastInstitution; + + public String getStudentCode() { + return studentCode; + } + + public void setStudentCode(String studentCode) { + this.studentCode = studentCode; + } + + public String getLastInstitution() { + return lastInstitution; + } + + public void setLastInstitution(String lastInstitution) { + this.lastInstitution = lastInstitution; + } +} diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/test/java/com/baeldung/gradle/multiplerepositories/MultipleRepositoryTest.java b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/test/java/com/baeldung/gradle/multiplerepositories/MultipleRepositoryTest.java new file mode 100644 index 0000000000..d0a72d9b3c --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/multiple-repositories/src/test/java/com/baeldung/gradle/multiplerepositories/MultipleRepositoryTest.java @@ -0,0 +1,19 @@ +package com.baeldung.gradle.multiplerepositories; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MultipleRepositoryTest { + + @Test + public void testPublishedPackage() { + Student student = new Student(); + student.setId(1); + student.setStudentCode("CD-875"); + student.setName("John Doe"); + student.setLastInstitution("Institute of Technology"); + + assertEquals("John Doe", student.getName()); + } +} diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/build.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/build.gradle new file mode 100644 index 0000000000..bd97650e9c --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/build.gradle @@ -0,0 +1,33 @@ +plugins { + id "maven-publish" + id "java" +} + +group = "com.baeldung.gradle" +version = "1.0.0-SNAPSHOT" + +repositories { + mavenLocal() + mavenCentral() +} + +publishing { + publications { + register("jar", MavenPublication) { + from(components["java"]) + pom { + url.set("https://github.com/eugenp/tutorials.git") + } + } + } + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/eugenp/tutorials" + credentials { + username = project.USERNAME + password = project.GITHUB_TOKEN + } + } + } +} diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.jar b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..41d9927a4d Binary files /dev/null and b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.properties b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..070cb702f0 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew.bat b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/settings.gradle b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/settings.gradle new file mode 100644 index 0000000000..9ccd121d0d --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "publish-package" diff --git a/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/src/main/java/com/baeldung/gradle/publish_package/User.java b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/src/main/java/com/baeldung/gradle/publish_package/User.java new file mode 100644 index 0000000000..bc612d038b --- /dev/null +++ b/gradle-modules/gradle-7/multiple-repositories-demo/publish-package/src/main/java/com/baeldung/gradle/publish_package/User.java @@ -0,0 +1,35 @@ +package com.baeldung.gradle.publish_package; + +import java.util.Date; + +public class User { + private Integer id; + + private String name; + + private Date dob; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDob() { + return dob; + } + + public void setDob(Date dob) { + this.dob = dob; + } +} diff --git a/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-fullpath-job b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-fullpath-job new file mode 100644 index 0000000000..98dc32efe5 --- /dev/null +++ b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-fullpath-job @@ -0,0 +1,11 @@ +pipeline { + agent any + stages { + stage('Build') { + steps { + dir('/var/jenkins_home/workspace/SamplePipeline/scripts') { + } + } + } + } +} diff --git a/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-job b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-job new file mode 100644 index 0000000000..42a946ca49 --- /dev/null +++ b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-dir-job @@ -0,0 +1,11 @@ + pipeline { + agent any + stages { + stage('Build') { + steps { + dir('scripts') { + } + } + } + } +} diff --git a/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-sh-job b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-sh-job new file mode 100644 index 0000000000..9b7f992b86 --- /dev/null +++ b/jenkins-modules/jenkins-jobs/change-directory-job/pipeline-sh-job @@ -0,0 +1,10 @@ +pipeline { + agent any + stages { + stage('Build') { + steps { + sh 'cd scripts' + } + } + } +} diff --git a/jenkins-modules/jenkins-jobs/dsl-error-fix/no-such-dsl-method-job b/jenkins-modules/jenkins-jobs/dsl-error-fix/no-such-dsl-method-job new file mode 100644 index 0000000000..f68cb70c2f --- /dev/null +++ b/jenkins-modules/jenkins-jobs/dsl-error-fix/no-such-dsl-method-job @@ -0,0 +1,10 @@ +pipeline { + agent any + stages { + stage('Build') { + steps { + mvn 'clean install' + } + } + } +} diff --git a/jersey/pom.xml b/jersey/pom.xml index c8a7de66ae..7c7330d84f 100644 --- a/jersey/pom.xml +++ b/jersey/pom.xml @@ -72,6 +72,11 @@ ${jersey.version} test + + org.glassfish.jersey.connectors + jersey-apache-connector + ${jersey.version} + @@ -95,7 +100,7 @@ - 2.26 + 2.38 \ No newline at end of file diff --git a/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java b/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java index ebcbe1d4ab..29db298e9e 100644 --- a/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java +++ b/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java @@ -1,6 +1,8 @@ package com.baeldung.jersey.client; import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter; +import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; +import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.glassfish.jersey.client.oauth1.AccessToken; import org.glassfish.jersey.client.oauth1.ConsumerCredentials; @@ -155,7 +157,8 @@ public class JerseyClientHeaders { } public static Response sendRestrictedHeaderThroughDefaultTransportConnector(String headerKey, String headerValue) { - Client client = ClientBuilder.newClient(); + ClientConfig clientConfig = new ClientConfig().connectorProvider(new ApacheConnectorProvider()); + Client client = ClientBuilder.newClient(clientConfig); System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); return client.target(TARGET) diff --git a/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java b/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java index cca7446dd1..24552f6d81 100644 --- a/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java +++ b/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java @@ -2,10 +2,8 @@ package com.baeldung.jersey.server; import com.baeldung.jersey.client.JerseyClientHeaders; import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter; -import org.glassfish.jersey.media.sse.SseFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Ignore; import org.junit.Test; import javax.ws.rs.core.Application; @@ -17,7 +15,6 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -@Ignore public class EchoHeadersUnitTest extends JerseyTest { private static final String SIMPLE_HEADER_KEY = "my-header-key"; diff --git a/lightrun/tasks-service/pom.xml b/lightrun/tasks-service/pom.xml index 2b3b37e51b..c3542b0089 100644 --- a/lightrun/tasks-service/pom.xml +++ b/lightrun/tasks-service/pom.xml @@ -24,6 +24,10 @@ org.springframework.boot spring-boot-starter-artemis + + org.springframework.boot + spring-boot-starter-cache + org.springframework.boot spring-boot-starter-data-jpa diff --git a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/SimpleCacheCustomizer.java b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/SimpleCacheCustomizer.java new file mode 100644 index 0000000000..bf8bb6ad6e --- /dev/null +++ b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/SimpleCacheCustomizer.java @@ -0,0 +1,16 @@ +package com.baeldung.tasksservice; + +import java.util.List; + +import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.stereotype.Component; + +@Component +public class SimpleCacheCustomizer implements CacheManagerCustomizer { + + @Override + public void customize(ConcurrentMapCacheManager cacheManager) { + cacheManager.setCacheNames(List.of("tasks")); + } +} \ No newline at end of file diff --git a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java index dfd9859674..84a8ed7967 100644 --- a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java +++ b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/TasksServiceApplication.java @@ -2,8 +2,10 @@ package com.baeldung.tasksservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication +@EnableCaching public class TasksServiceApplication { public static void main(String[] args) { diff --git a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java index 3539dbbc3c..107bf56bb9 100644 --- a/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java +++ b/lightrun/tasks-service/src/main/java/com/baeldung/tasksservice/service/TasksService.java @@ -19,6 +19,7 @@ import java.util.UUID; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import com.baeldung.tasksservice.adapters.repository.TaskRecord; @@ -29,6 +30,7 @@ public class TasksService { @Autowired private TasksRepository tasksRepository; + @Cacheable("tasks") public TaskRecord getTaskById(String id) { return tasksRepository.findById(id) .orElseThrow(() -> new UnknownTaskException(id)); diff --git a/logging-modules/logback/pom.xml b/logging-modules/logback/pom.xml index ab778e954e..937a88da09 100644 --- a/logging-modules/logback/pom.xml +++ b/logging-modules/logback/pom.xml @@ -24,6 +24,11 @@ logback-classic ${logback.version} + + ch.qos.logback + logback-core + ${logback.version} + ch.qos.logback.contrib logback-json-classic @@ -34,6 +39,11 @@ logback-jackson ${logback.contrib.version} + + org.slf4j + slf4j-api + ${slf4j.version} + com.fasterxml.jackson.core jackson-databind @@ -55,13 +65,13 @@ - javax.mail - mail - ${javax.mail.version} + com.sun.mail + javax.mail + ${javax.mail.version} javax.activation - activation + javax.activation-api ${javax.activation.version} runtime @@ -106,8 +116,10 @@ 20180130 0.1.5 3.3.5 - 1.4.7 - 1.1.1 + 1.6.2 + 1.2.0 + 1.3.5 + 2.0.4 \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java index 9ec14884f4..726764b411 100644 --- a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/query/datetime/ArticleRepository.java @@ -14,6 +14,9 @@ public interface ArticleRepository extends JpaRepository { List
findAllByPublicationTimeBetween(Date publicationTimeStart, Date publicationTimeEnd); + Article findByPublicationTimeBetween(Date publicationTimeStart, + Date publicationTimeEnd); + @Query("select a from Article a where a.creationDateTime <= :creationDateTime") List
findAllWithCreationDateTimeBefore( @Param("creationDateTime") Date creationDateTime); diff --git a/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/query/nonuniqueresultexception/NonUniqueResultExceptionIntegrationTest.java b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/query/nonuniqueresultexception/NonUniqueResultExceptionIntegrationTest.java new file mode 100644 index 0000000000..b032819ad6 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/query/nonuniqueresultexception/NonUniqueResultExceptionIntegrationTest.java @@ -0,0 +1,35 @@ +package com.baeldung.spring.data.jpa.query.nonuniqueresultexception; + +import com.baeldung.spring.data.jpa.query.datetime.ArticleRepository; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.persistence.NonUniqueResultException; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +@RunWith(SpringRunner.class) +@DataJpaTest(properties = "spring.sql.init.data-locations=classpath:import_entities.sql", showSql = false) +public class NonUniqueResultExceptionIntegrationTest { + + @Autowired + private ArticleRepository repository; + + @Test + public void givenImportedArticles_whenFindByPublicationTimeBetween_thenIncorrectResultSizeDataAccessExceptionThrown() { + assertThatThrownBy(() -> repository.findByPublicationTimeBetween(new SimpleDateFormat("HH:mm").parse("15:15"), new SimpleDateFormat("HH:mm").parse("16:30"))) + .isInstanceOf(IncorrectResultSizeDataAccessException.class) + .hasCauseInstanceOf(NonUniqueResultException.class); + } + + @Test + public void givenImportedArticles_whenFindAllByPublicationTimeBetween_thenSuccess() throws ParseException { + repository.findAllByPublicationTimeBetween(new SimpleDateFormat("HH:mm").parse("15:15"), new SimpleDateFormat("HH:mm").parse("16:30")); + } +} diff --git a/persistence-modules/spring-data-jpa-repo-2/pom.xml b/persistence-modules/spring-data-jpa-repo-2/pom.xml index 9240a00ee2..dd0406eca8 100644 --- a/persistence-modules/spring-data-jpa-repo-2/pom.xml +++ b/persistence-modules/spring-data-jpa-repo-2/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 spring-data-jpa-repo-2 spring-data-jpa-repo-2 @@ -34,6 +34,14 @@ com.h2database h2 + + com.querydsl + querydsl-apt + + + com.querydsl + querydsl-jpa + com.google.guava guava @@ -41,4 +49,53 @@ + + + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + generate-sources + + process + + + ${project.build.directory}/generated-sources + com.querydsl.apt.jpa.JPAAnnotationProcessor + + + + + + org.bsc.maven + maven-processor-plugin + 3.3.3 + + + process + + process + + generate-sources + + ${project.build.directory}/generated-sources + + org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor + + + + + + + org.hibernate + hibernate-jpamodelgen + 5.6.11.Final + + + + + + \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/model/Employee.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/model/Employee.java new file mode 100644 index 0000000000..9690bcf68a --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/model/Employee.java @@ -0,0 +1,76 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference.model; + +import java.io.Serializable; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +@Entity +@Table(name = "employee") +@NamedQuery(name = "Employee.findById", query = "SELECT e FROM Employee e WHERE e.id = :id") +public class Employee implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + @Column(nullable = false) + private String firstName; + @Column(nullable = false) + private String lastName; + + @Column(nullable = false) + private String email; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Employee employee = (Employee) o; + return Objects.equals(id, employee.id) && Objects.equals(firstName, employee.firstName) && Objects.equals(lastName, employee.lastName) && Objects.equals(email, employee.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName, email); + } +} diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/config/SpringDataJpaConfig.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/config/SpringDataJpaConfig.java new file mode 100644 index 0000000000..57f9d2981f --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/config/SpringDataJpaConfig.java @@ -0,0 +1,66 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference.springdata.config; + +import java.util.Properties; + +import javax.persistence.EntityManager; +import javax.sql.DataSource; + +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository.EmployeeRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; + +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories(basePackageClasses = EmployeeRepository.class) +public class SpringDataJpaConfig { + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { + LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource); + em.setPackagesToScan("com.baeldung.spring.data.persistence.springdata_jpa_difference.model"); + + JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + em.setJpaVendorAdapter(vendorAdapter); + + Properties properties = new Properties(); + properties.setProperty("hibernate.hbm2ddl.auto", "create-drop"); + properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + + em.setJpaProperties(properties); + + return em; + } + + @Bean + public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject()); + return transactionManager; + } + + @Bean + public DataSource dataSource() { + return DataSourceBuilder.create() + .url("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1") + .driverClassName("org.h2.Driver") + .username("sa") + .password("sa") + .build(); + } + + @Bean + public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) { + return new JPAQueryFactory((entityManager)); + } +} diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepository.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepository.java new file mode 100644 index 0000000000..012a46d885 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepository.java @@ -0,0 +1,19 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository; + +import java.util.List; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee; + +@Repository +public interface EmployeeRepository extends JpaRepository { + + List findByFirstName(String firstName); + + @Query(value = "SELECT e FROM Employee e") + List findAllEmployee(Sort sort); +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepositoryPagingAndSort.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepositoryPagingAndSort.java new file mode 100644 index 0000000000..731735ea62 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/springdatajpadifference/springdata/repository/EmployeeRepositoryPagingAndSort.java @@ -0,0 +1,11 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee; + +@Repository +public interface EmployeeRepositoryPagingAndSort extends PagingAndSortingRepository { + +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/JpaDaoIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/JpaDaoIntegrationTest.java new file mode 100644 index 0000000000..b25038f175 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/JpaDaoIntegrationTest.java @@ -0,0 +1,201 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference; + +import static com.baeldung.spring.data.persistence.springdatajpadifference.TestUtils.employee; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.criteria.Root; + +import org.junit.Before; +import org.junit.Test; + +import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee; +import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee_; + +public class JpaDaoIntegrationTest { + + private final EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu-test"); + private final EntityManager entityManager = emf.createEntityManager(); + + @Before + public void setup() { + deleteAllEmployees(); + } + + @Test + public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + save(employee); + + assertEquals(employee, entityManager.find(Employee.class, employee.getId())); + } + + @Test + public void givenPersistedEmployee_whenFindByIdCriteriaQuery_thenEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + save(employee); + + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Employee.class); + Root root = criteriaQuery.from(Employee.class); + criteriaQuery.select(root); + + criteriaQuery.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId())); + + assertEquals(employee, entityManager.createQuery(criteriaQuery) + .getSingleResult()); + } + + @Test + public void givenPersistedEmployee_whenFindByIdJpql_thenEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + save(employee); + + Query jpqlQuery = entityManager.createQuery("SELECT e from Employee e WHERE e.id=:id"); + jpqlQuery.setParameter("id", employee.getId()); + + assertEquals(employee, jpqlQuery.getSingleResult()); + } + + @Test + public void givenPersistedEmployee_whenFindByIdNamedQuery_thenEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + save(employee); + + Query query = entityManager.createNamedQuery("Employee.findById"); + + query.setParameter(Employee_.ID, employee.getId()); + + assertEquals(employee, query.getSingleResult()); + } + + @Test + public void givenPersistedEmployee_whenFindWithPaginationAndSort_thenEmployeesAreFound() { + Employee john = employee("John", "Doe"); + Employee bob = employee("Bob", "Smith"); + Employee frank = employee("Frank", "Brown"); + Employee james = employee("James", "Smith"); + save(john); + save(bob); + save(frank); + save(james); + + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Employee.class); + Root root = criteriaQuery.from(Employee.class); + criteriaQuery.select(root); + criteriaQuery.orderBy(criteriaBuilder.asc(root.get(Employee_.FIRST_NAME))); + + TypedQuery query = entityManager.createQuery(criteriaQuery); + + query.setFirstResult(0); + query.setMaxResults(3); + + List employeeList = query.getResultList(); + + assertEquals(Arrays.asList(bob, frank, james), employeeList); + } + + @Test + public void givenPersistedEmployee_whenUpdateEmployeeEmail_thenEmployeeHasUpdatedEmail() { + Employee employee = employee("John", "Doe"); + save(employee); + + Employee employeeToUpdate = entityManager.find(Employee.class, employee.getId()); + + String updatedEmail = "email@gmail.com"; + + employeeToUpdate.setEmail(updatedEmail); + + update(employeeToUpdate); + + assertEquals(updatedEmail, entityManager.find(Employee.class, employee.getId()) + .getEmail()); + } + + @Test + public void givenPersistedEmployee_whenUpdateEmployeeEmailWithCriteria_thenEmployeeHasUpdatedEmail() { + Employee employee = employee("John", "Doe"); + save(employee); + + String updatedEmail = "email@gmail.com"; + + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate(Employee.class); + Root root = criteriaUpdate.from(Employee.class); + + criteriaUpdate.set(Employee_.EMAIL, updatedEmail); + criteriaUpdate.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId())); + + assertEquals(1, update(criteriaUpdate)); + + assertEquals(updatedEmail, entityManager.find(Employee.class, employee.getId()) + .getEmail()); + } + + @Test + public void givenPersistedEmployee_whenRemoveEmployee_thenNoEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + save(employee); + + delete(employee.getId()); + + assertNull(entityManager.find(Employee.class, employee.getId())); + } + + private void deleteAllEmployees() { + entityManager.getTransaction() + .begin(); + entityManager.createNativeQuery("DELETE from Employee") + .executeUpdate(); + entityManager.getTransaction() + .commit(); + } + + public void save(Employee entity) { + entityManager.getTransaction() + .begin(); + entityManager.persist(entity); + entityManager.getTransaction() + .commit(); + } + + public void update(Employee entity) { + entityManager.getTransaction() + .begin(); + entityManager.merge(entity); + entityManager.getTransaction() + .commit(); + } + + public void delete(Long employee) { + entityManager.getTransaction() + .begin(); + entityManager.remove(entityManager.find(Employee.class, employee)); + entityManager.getTransaction() + .commit(); + } + + public int update(CriteriaUpdate criteriaUpdate) { + entityManager.getTransaction() + .begin(); + int result = entityManager.createQuery(criteriaUpdate) + .executeUpdate(); + entityManager.getTransaction() + .commit(); + entityManager.clear(); + + return result; + } +} diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/SpringDataJpaIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/SpringDataJpaIntegrationTest.java new file mode 100644 index 0000000000..e6febdc9f1 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/SpringDataJpaIntegrationTest.java @@ -0,0 +1,153 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference; + +import static com.baeldung.spring.data.persistence.springdatajpadifference.TestUtils.employee; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee; +import com.baeldung.spring.data.persistence.springdatajpadifference.model.QEmployee; +import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.config.SpringDataJpaConfig; +import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository.EmployeeRepository; +import com.baeldung.spring.data.persistence.springdatajpadifference.springdata.repository.EmployeeRepositoryPagingAndSort; +import com.querydsl.jpa.impl.JPAQueryFactory; + +@ContextConfiguration(classes = SpringDataJpaConfig.class) +@RunWith(SpringJUnit4ClassRunner.class) +@Transactional +@Rollback +public class SpringDataJpaIntegrationTest { + + @Autowired + private EmployeeRepository employeeRepository; + + @Autowired + private EmployeeRepositoryPagingAndSort employeeRepositoryPagingAndSort; + + @Autowired + private JPAQueryFactory jpaQueryFactory; + + @Test + public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + + employeeRepository.save(employee); + + assertEquals(Optional.of(employee), employeeRepository.findById(employee.getId())); + } + + @Test + public void givenPersistedEmployee_whenFindByFirstName_thenEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + + employeeRepository.save(employee); + + assertEquals(employee, employeeRepository.findByFirstName(employee.getFirstName()) + .get(0)); + } + + @Test + public void givenPersistedEmployee_whenUpdateEmployeeEmail_thenEmployeeHasUpdatedEmail() { + Employee employee = employee("John", "Doe"); + + employeeRepository.save(employee); + + Employee employeeToUpdate = employeeRepository.findById(employee.getId()) + .orElse(null); + + assertNotNull(employeeToUpdate); + assertEquals(employee, employeeToUpdate); + + String updatedEmail = "email@gmail.com"; + + employeeToUpdate.setEmail(updatedEmail); + + employeeRepository.save(employeeToUpdate); + + assertEquals(Optional.of(employeeToUpdate), employeeRepository.findById(employee.getId())); + } + + @Test + public void givenPersistedEmployee_whenRemoveEmployee_thenNoEmployeeIsFound() { + Employee employee = employee("John", "Doe"); + + employeeRepository.save(employee); + + Employee persistedEmployee = employeeRepository.findById(employee.getId()) + .orElse(null); + + assertNotNull(persistedEmployee); + + employeeRepository.delete(persistedEmployee); + + assertFalse(employeeRepository.findById(employee.getId()) + .isPresent()); + } + + @Test + public void givenPersistedEmployees_whenFindSortedByFirstName_thenEmployeeAreFoundInOrder() { + Employee john = employee("John", "Doe"); + Employee bob = employee("Bob", "Smith"); + Employee frank = employee("Frank", "Brown"); + + employeeRepository.saveAll(Arrays.asList(john, bob, frank)); + + List employees = employeeRepository.findAllEmployee(Sort.by("firstName")); + + assertEquals(3, employees.size()); + assertEquals(bob, employees.get(0)); + assertEquals(frank, employees.get(1)); + assertEquals(john, employees.get(2)); + } + + @Test + public void givenPersistedEmployee_whenFindByQueryDsl_thenEmployeeIsFound() { + Employee john = employee("John", "Doe"); + Employee frank = employee("Frank", "Doe"); + + employeeRepository.saveAll(Arrays.asList(john, frank)); + + QEmployee employeePath = QEmployee.employee; + + List employees = jpaQueryFactory.selectFrom(employeePath) + .where(employeePath.firstName.eq("John"), employeePath.lastName.eq("Doe")) + .fetch(); + + assertEquals(1, employees.size()); + assertEquals(john, employees.get(0)); + } + + @Test + public void givenPersistedEmployee_whenFindBySortAndPagingRepository_thenEmployeeAreFound() { + Employee john = employee("John", "Doe"); + Employee bob = employee("Bob", "Smith"); + Employee frank = employee("Frank", "Brown"); + Employee jimmy = employee("Jimmy", "Armstrong"); + + employeeRepositoryPagingAndSort.saveAll(Arrays.asList(john, bob, frank, jimmy)); + + Pageable pageable = PageRequest.of(0, 2, Sort.by("firstName")); + + Page employees = employeeRepositoryPagingAndSort.findAll(pageable); + + assertEquals(Arrays.asList(bob, frank), employees.get() + .collect(Collectors.toList())); + } +} diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/TestUtils.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/TestUtils.java new file mode 100644 index 0000000000..989a7db247 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/springdatajpadifference/TestUtils.java @@ -0,0 +1,15 @@ +package com.baeldung.spring.data.persistence.springdatajpadifference; + +import com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee; + +public class TestUtils { + + public static Employee employee(String firstName, String lastname) { + Employee employee = new Employee(); + employee.setFirstName(firstName); + employee.setLastName(lastname); + employee.setEmail(firstName + lastname + "@baeldung.com"); + + return employee; + } +} diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/resources/META-INF/persistence.xml b/persistence-modules/spring-data-jpa-repo-2/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..94df50cf0a --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,21 @@ + + + + org.hibernate.jpa.HibernatePersistenceProvider + com.baeldung.spring.data.persistence.springdatajpadifference.model.Employee + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index a5bdd2cf4c..3d2863e1f2 100644 --- a/pom.xml +++ b/pom.xml @@ -1168,6 +1168,7 @@ spring-boot-modules/spring-boot-camel spring-boot-modules/spring-boot-3 spring-boot-modules/spring-boot-3-native + spring-boot-modules/spring-boot-3-observation spring-swagger-codegen/custom-validations-opeanpi-codegen testing-modules/testing-assertions persistence-modules/fauna @@ -1251,6 +1252,7 @@ spring-boot-modules/spring-boot-camel spring-boot-modules/spring-boot-3 spring-boot-modules/spring-boot-3-native + spring-boot-modules/spring-boot-3-observation spring-swagger-codegen/custom-validations-opeanpi-codegen testing-modules/testing-assertions persistence-modules/fauna diff --git a/spring-boot-modules/spring-boot-3-observation/pom.xml b/spring-boot-modules/spring-boot-3-observation/pom.xml new file mode 100644 index 0000000000..ed613ee98e --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + spring-boot-3-observation + 0.0.1-SNAPSHOT + spring-boot-3-observation + Demo project for Spring Boot 3 Observation + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + io.micrometer + micrometer-tracing + + + io.micrometer + micrometer-tracing-bridge-brave + + + + io.micrometer + micrometer-observation-test + test + + + io.micrometer + micrometer-tracing-test + test + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/GreetingApplication.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/GreetingApplication.java new file mode 100644 index 0000000000..f5014a8abd --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/GreetingApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.samples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GreetingApplication { + + public static void main(String[] args) { + SpringApplication.run(GreetingApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/SimpleObservationApplication.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/SimpleObservationApplication.java new file mode 100644 index 0000000000..4434535939 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/SimpleObservationApplication.java @@ -0,0 +1,66 @@ +package com.baeldung.samples; + +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.ObservationTextPublisher; + +import java.util.Optional; +import java.util.stream.StreamSupport; + +public class SimpleObservationApplication { + + // we can run this as a simple command line application + public static void main(String[] args) { + // create registry + final var observationRegistry = ObservationRegistry.create(); + // create meter registry and observation handler + final var meterRegistry = new SimpleMeterRegistry(); + final var meterObservationHandler = new DefaultMeterObservationHandler(meterRegistry); + // create simple logging observation handler + final var loggingObservationHandler = new ObservationTextPublisher(System.out::println); + // register observation handlers + observationRegistry + .observationConfig() + .observationHandler(meterObservationHandler) + .observationHandler(loggingObservationHandler); + // make an observation + Observation.Context context = new Observation.Context(); + String observationName = "obs1"; + Observation observation = Observation + .createNotStarted(observationName, () -> context, observationRegistry) + .lowCardinalityKeyValue("gender", "male") + .highCardinalityKeyValue("age", "41"); + + for (int i = 0; i < 10; i++) { + observation.observe(SimpleObservationApplication::doSomeAction); + } + + meterRegistry.getMeters().forEach(m -> { + System.out.println(m.getId() + "\n============"); + m.measure().forEach(ms -> System.out.println(ms.getValue() + " [" + ms.getStatistic() + "]")); + System.out.println("----------------------------"); + }); + Optional maximumDuration = meterRegistry.getMeters().stream() + .filter(m -> "obs1".equals(m.getId().getName())) + .flatMap(m -> StreamSupport.stream(m.measure().spliterator(), false)) + .filter(ms -> ms.getStatistic() == Statistic.MAX) + .findFirst() + .map(Measurement::getValue); + + System.out.println(maximumDuration); + } + + private static void doSomeAction() { + try { + Thread.sleep(Math.round(Math.random() * 1000)); + System.out.println("Hello World!"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/GreetingController.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/GreetingController.java new file mode 100644 index 0000000000..bc179540f8 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/GreetingController.java @@ -0,0 +1,26 @@ +package com.baeldung.samples.boundary; + +import com.baeldung.samples.domain.GreetingService; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/greet") +public class GreetingController { + + private final GreetingService service; + + public GreetingController(GreetingService service) { + this.service = service; + } + + @GetMapping(produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String sayHello() { + return this.service.sayHello(); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/ObservationFilterConfiguration.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/ObservationFilterConfiguration.java new file mode 100644 index 0000000000..c39af961a1 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/boundary/ObservationFilterConfiguration.java @@ -0,0 +1,22 @@ +package com.baeldung.samples.boundary; + +import io.micrometer.observation.ObservationRegistry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ServerHttpObservationFilter; + +@Configuration +public class ObservationFilterConfiguration { + + // if an ObservationRegistry is already configured + @ConditionalOnBean(ObservationRegistry.class) + // if we do not use Actuator + @ConditionalOnMissingBean(ServerHttpObservationFilter.class) + @Bean + public ServerHttpObservationFilter observationFilter(ObservationRegistry registry) { + return new ServerHttpObservationFilter(registry); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationHandlerLogger.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationHandlerLogger.java new file mode 100644 index 0000000000..0a1f52f9c1 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationHandlerLogger.java @@ -0,0 +1,28 @@ +package com.baeldung.samples.config; + +import io.micrometer.observation.ObservationHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class ObservationHandlerLogger { + + private static final Logger log = LoggerFactory.getLogger(ObservationHandlerLogger.class); + + private static String toString(ObservationHandler handler) { + return handler.getClass().getName() + " [ " + handler + "]"; + } + + @EventListener(ContextRefreshedEvent.class) + public void logObservationHandlers(ContextRefreshedEvent evt) { + evt.getApplicationContext().getBeansOfType(ObservationHandler.class) + .values() + .stream() + .map(ObservationHandlerLogger::toString) + .forEach(log::info); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationTextPublisherConfiguration.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationTextPublisherConfiguration.java new file mode 100644 index 0000000000..29637166c9 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservationTextPublisherConfiguration.java @@ -0,0 +1,21 @@ +package com.baeldung.samples.config; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.ObservationTextPublisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObservationTextPublisherConfiguration { + + private static final Logger log = LoggerFactory.getLogger(ObservationTextPublisherConfiguration.class); + + @Bean + public ObservationHandler observationTextPublisher() { + return new ObservationTextPublisher(log::info); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservedAspectConfiguration.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservedAspectConfiguration.java new file mode 100644 index 0000000000..cd475113c7 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/ObservedAspectConfiguration.java @@ -0,0 +1,20 @@ +package com.baeldung.samples.config; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +@ConditionalOnClass(ObservedAspect.class) +public class ObservedAspectConfiguration { + + @Bean + @ConditionalOnMissingBean + public ObservedAspect observedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/SimpleLoggingHandler.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/SimpleLoggingHandler.java new file mode 100644 index 0000000000..c87aa68085 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/config/SimpleLoggingHandler.java @@ -0,0 +1,59 @@ +package com.baeldung.samples.config; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class SimpleLoggingHandler implements ObservationHandler { + + private static final Logger log = LoggerFactory.getLogger(SimpleLoggingHandler.class); + + private static String toString(Observation.Context context) { + return null == context ? "(no context)" : context.getName() + + " (" + context.getClass().getName() + "@" + System.identityHashCode(context) + ")"; + } + + private static String toString(Observation.Event event) { + return null == event ? "(no event)" : event.getName(); + } + + @Override + public boolean supportsContext(Observation.Context context) { + return true; + } + + @Override + public void onStart(Observation.Context context) { + log.info("Starting context " + toString(context)); + } + + @Override + public void onError(Observation.Context context) { + log.info("Error for context " + toString(context)); + } + + @Override + public void onEvent(Observation.Event event, Observation.Context context) { + log.info("Event for context " + toString(context) + " [" + toString(event) + "]"); + } + + @Override + public void onScopeOpened(Observation.Context context) { + log.info("Scope opened for context " + toString(context)); + + } + + @Override + public void onScopeClosed(Observation.Context context) { + log.info("Scope closed for context " + toString(context)); + } + + @Override + public void onStop(Observation.Context context) { + log.info("Stopping context " + toString(context)); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/domain/GreetingService.java b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/domain/GreetingService.java new file mode 100644 index 0000000000..ec362dd3cc --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/java/com/baeldung/samples/domain/GreetingService.java @@ -0,0 +1,14 @@ +package com.baeldung.samples.domain; + +import io.micrometer.observation.annotation.Observed; +import org.springframework.stereotype.Service; + +@Observed(name = "greetingService") +@Service +public class GreetingService { + + public String sayHello() { + return "Hello World!"; + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/main/resources/application.yml b/spring-boot-modules/spring-boot-3-observation/src/main/resources/application.yml new file mode 100644 index 0000000000..9f91e8a03a --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/main/resources/application.yml @@ -0,0 +1,6 @@ +management: + endpoints: + web: + exposure: + include: '*' + #health,info,beans,metrics,startup diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/config/SimpleLoggingHandlerUnitTest.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/config/SimpleLoggingHandlerUnitTest.java new file mode 100644 index 0000000000..5a6d1bd23f --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/config/SimpleLoggingHandlerUnitTest.java @@ -0,0 +1,17 @@ +package com.baeldung.samples.config; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.tck.AnyContextObservationHandlerCompatibilityKit; + +class SimpleLoggingHandlerUnitTest + extends AnyContextObservationHandlerCompatibilityKit { + + SimpleLoggingHandler handler = new SimpleLoggingHandler(); + + @Override + public ObservationHandler handler() { + return handler; + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/EnableTestObservation.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/EnableTestObservation.java new file mode 100644 index 0000000000..8e4e2a1da0 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/EnableTestObservation.java @@ -0,0 +1,44 @@ +package com.baeldung.samples.domain; + +import com.baeldung.samples.config.ObservedAspectConfiguration; +import io.micrometer.observation.tck.TestObservationRegistry; +import io.micrometer.tracing.test.simple.SimpleTracer; +import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@AutoConfigureObservability +@Import({ + ObservedAspectConfiguration.class, + EnableTestObservation.ObservationTestConfiguration.class +}) +public @interface EnableTestObservation { + + @TestConfiguration + class ObservationTestConfiguration { + + @Bean + TestObservationRegistry observationRegistry() { + return TestObservationRegistry.create(); + } + + @Bean + SimpleTracer simpleTracer() { + return new SimpleTracer(); + } + + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceObservationIntegrationTest.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceObservationIntegrationTest.java new file mode 100644 index 0000000000..98fa175660 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceObservationIntegrationTest.java @@ -0,0 +1,35 @@ +package com.baeldung.samples.domain; + +import io.micrometer.observation.tck.TestObservationRegistry; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static io.micrometer.observation.tck.TestObservationRegistryAssert.assertThat; + +@ExtendWith(SpringExtension.class) +@ComponentScan(basePackageClasses = GreetingService.class) +@EnableAutoConfiguration +@EnableTestObservation +class GreetingServiceObservationIntegrationTest { + + @Autowired + GreetingService service; + @Autowired + TestObservationRegistry registry; + + @Test + void testObservation() { + // invoke service + service.sayHello(); + assertThat(registry) + .hasObservationWithNameEqualTo("greetingService") + .that() + .hasBeenStarted() + .hasBeenStopped(); + } + +} diff --git a/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceTracingIntegrationTest.java b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceTracingIntegrationTest.java new file mode 100644 index 0000000000..0199c0e7ef --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/src/test/java/com/baeldung/samples/domain/GreetingServiceTracingIntegrationTest.java @@ -0,0 +1,42 @@ +package com.baeldung.samples.domain; + +import io.micrometer.tracing.test.simple.SimpleTracer; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static io.micrometer.tracing.test.simple.TracerAssert.assertThat; + +@ExtendWith(SpringExtension.class) +@ComponentScan(basePackageClasses = GreetingService.class) +@EnableAutoConfiguration +@EnableTestObservation +class GreetingServiceTracingIntegrationTest { + + @Autowired + GreetingService service; + @Value("${management.tracing.enabled:true}") + boolean tracingEnabled; + @Autowired + SimpleTracer tracer; + + @Test + void testEnabledTracing() { + Assertions.assertThat(tracingEnabled).isTrue(); + } + + @Test + void testTracingForGreeting() { + service.sayHello(); + assertThat(tracer) + .onlySpan() + .hasNameEqualTo("greeting-service#say-hello") + .isEnded(); + } + +} diff --git a/spring-boot-modules/spring-boot-graphql/pom.xml b/spring-boot-modules/spring-boot-graphql/pom.xml index b4b449166f..bb475679ad 100644 --- a/spring-boot-modules/spring-boot-graphql/pom.xml +++ b/spring-boot-modules/spring-boot-graphql/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 spring-boot-graphql spring-boot-graphql @@ -13,6 +13,47 @@ 1.0.0-SNAPSHOT + + + + kr.motd.maven + os-maven-plugin + 1.7.0 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-plugin.version} + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + + + + 3.19.2 + 0.6.1 + 1.43.2 + 2.13.1.RELEASE + 1.5.1 + 1.3.5 + 1.6.2 + 3.3.2 + + org.springframework.boot @@ -78,45 +119,4 @@ - - - - kr.motd.maven - os-maven-plugin - 1.7.0 - - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - ${protobuf-plugin.version} - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - grpc-java - io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} - - - - - compile - compile-custom - - - - - - - - - 3.19.2 - 0.6.1 - 1.43.2 - 2.13.1.RELEASE - 1.5.1 - 1.3.5 - 1.6.2 - 3.3.2 - - \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml b/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml index 889842df9f..0036bb3284 100644 --- a/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml +++ b/spring-boot-modules/spring-boot-graphql/src/main/resources/application-chooseapi.yml @@ -2,8 +2,10 @@ server: port: 8082 spring: + main: + allow-bean-definition-overriding: true graphql: graphiql: enabled: true schema: - locations: classpath:chooseapi/ \ No newline at end of file + locations: classpath:chooseapi/ diff --git a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java index c1ce711388..48ed73fbde 100644 --- a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java +++ b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerGraphQLIntegrationTest.java @@ -11,7 +11,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ChooseApiApp.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "grpc.server.port=-1" }, // Disable gRPC external server + classes = ChooseApiApp.class) @ActiveProfiles("chooseapi") class BooksControllerGraphQLIntegrationTest { diff --git a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java index 977a132653..4f2f8e8e51 100644 --- a/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java +++ b/spring-boot-modules/spring-boot-graphql/src/test/java/com/baeldung/chooseapi/controllers/BooksControllerRestIntegrationTest.java @@ -9,13 +9,15 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -@SpringBootTest +@SpringBootTest(properties = { "grpc.server.port=-1" }) // Disable gRPC external server +@ActiveProfiles("chooseapi") @AutoConfigureMockMvc class BooksControllerRestIntegrationTest { diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml index c7d3f5d12c..397f06399f 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/pom.xml @@ -1,44 +1,54 @@ - 4.0.0 - spring-cloud-stream-kinesis - spring-cloud-stream-kinesis + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + spring-cloud-stream-kinesis + spring-cloud-stream-kinesis - - com.baeldung - spring-cloud-stream - 1.0.0-SNAPSHOT - + + com.baeldung + spring-cloud-stream + 1.0.0-SNAPSHOT + - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-stream-binder-kinesis - ${spring-cloud-stream-kinesis-binder.version} - - - com.amazonaws - aws-java-sdk-kinesis - ${aws-sdk.version} - - - org.springframework.cloud - spring-cloud-stream-test-support - ${spring-cloud-stream-test.version} - test - - + + + org.springframework.boot + spring-boot-starter-web + + + com.amazonaws + aws-java-sdk-kinesis + ${aws-sdk.version} + + + org.springframework.cloud + spring-cloud-stream-test-support + ${spring-cloud-stream-test.version} + test + + + com.amazonaws + amazon-kinesis-producer + 0.13.1 + + + com.amazonaws + amazon-kinesis-client + 1.11.2 + + + org.springframework.cloud + spring-cloud-stream-binder-kinesis + ${spring-cloud-stream-kinesis-binder.version} + + - - 1.11.632 - 2.0.2.RELEASE - 2.2.1.RELEASE - + + 1.11.632 + 2.0.2.RELEASE + 2.2.1.RELEASE + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/KinesisApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/KinesisApplication.java deleted file mode 100644 index 6926560244..0000000000 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/KinesisApplication.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.baeldung; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.messaging.Processor; -import org.springframework.context.annotation.Bean; -import org.springframework.messaging.support.MessageBuilder; - -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.kinesis.AmazonKinesis; -import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; - -@SpringBootApplication -@EnableBinding(Processor.class) -public class KinesisApplication { - - @Value("${aws.access.key}") - private String accessKey; - - @Value("${aws.secret.key}") - private String secretKey; - - @Autowired - private Processor processor; - - public static void main(String[] args) { - SpringApplication.run(KinesisApplication.class, args); - } - - @Bean - public AmazonKinesis buildAmazonKinesis() { - BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); - return AmazonKinesisClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) - .withRegion(Regions.EU_CENTRAL_1) - .build(); - } - - @StreamListener(Processor.INPUT) - public void consume(String val) { - System.out.println(val); - } - - public void produce(String val) { - processor.output().send(MessageBuilder.withPayload(val).build()); - } -} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ConsumerBinder.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ConsumerBinder.java new file mode 100644 index 0000000000..38ad634086 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ConsumerBinder.java @@ -0,0 +1,16 @@ +package com.baeldung.binder; + +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.annotation.StreamListener; +import org.springframework.cloud.stream.messaging.Sink; +import org.springframework.stereotype.Component; + +@Component +@EnableBinding(Sink.class) +public class ConsumerBinder { + + @StreamListener(Sink.INPUT) + public void consume(String ip) { + System.out.println(ip); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/KinesisBinderApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/KinesisBinderApplication.java new file mode 100644 index 0000000000..e4f6916ed9 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/KinesisBinderApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.binder; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class KinesisBinderApplication { + + public static void main(String[] args) { + SpringApplication.run(KinesisBinderApplication.class, args); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ProducerBinder.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ProducerBinder.java new file mode 100644 index 0000000000..468f2886de --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/binder/ProducerBinder.java @@ -0,0 +1,24 @@ +package com.baeldung.binder; + +import java.util.stream.IntStream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.messaging.Source; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@EnableBinding(Source.class) +public class ProducerBinder { + + @Autowired + private Source source; + + @Scheduled(fixedDelay = 3000L) + private void produce() { + IntStream.range(1, 200).mapToObj(ipSuffix -> "192.168.0." + ipSuffix) + .forEach(entry -> source.output().send(MessageBuilder.withPayload(entry).build())); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessor.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessor.java similarity index 96% rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessor.java rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessor.java index 32e6babc86..c028f530dc 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessor.java +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessor.java @@ -1,4 +1,4 @@ -package com.baeldung; +package com.baeldung.kclkpl; import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor; import com.amazonaws.services.kinesis.clientlibrary.types.InitializationInput; diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessorFactory.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessorFactory.java similarity index 92% rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessorFactory.java rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessorFactory.java index 1ca774bb39..7515e65eff 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProcessorFactory.java +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProcessorFactory.java @@ -1,4 +1,4 @@ -package com.baeldung; +package com.baeldung.kclkpl; import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor; import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessorFactory; diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProducer.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProducer.java new file mode 100644 index 0000000000..76111cfe57 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/IpProducer.java @@ -0,0 +1,30 @@ +package com.baeldung.kclkpl; + +import java.nio.ByteBuffer; +import java.util.stream.IntStream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.amazonaws.services.kinesis.producer.KinesisProducer; + +@Component +public class IpProducer { + + @Value("${ips.stream}") + private String IPS_STREAM; + + @Value("${ips.partition.key}") + private String IPS_PARTITION_KEY; + + @Autowired + private KinesisProducer kinesisProducer; + + @Scheduled(fixedDelay = 3000L) + private void produce() { + IntStream.range(1, 200).mapToObj(ipSuffix -> ByteBuffer.wrap(("192.168.0." + ipSuffix).getBytes())) + .forEach(entry -> kinesisProducer.addUserRecord(IPS_STREAM, IPS_PARTITION_KEY, entry)); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKCLApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKCLApplication.java new file mode 100644 index 0000000000..01c5af596d --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKCLApplication.java @@ -0,0 +1,48 @@ +package com.baeldung.kclkpl; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.kinesis.clientlibrary.lib.worker.KinesisClientLibConfiguration; +import com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker; + +@SpringBootApplication +public class KinesisKCLApplication implements ApplicationRunner { + + @Value("${aws.access.key}") + private String accessKey; + + @Value("${aws.secret.key}") + private String secretKey; + + @Value("${ips.stream}") + private String IPS_STREAM; + + public static void main(String[] args) { + SpringApplication.run(KinesisKCLApplication.class, args); + } + + @Override + public void run(ApplicationArguments args) throws Exception { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + KinesisClientLibConfiguration consumerConfig = new KinesisClientLibConfiguration( + "KinesisKCLConsumer", + IPS_STREAM, + new AWSStaticCredentialsProvider(awsCredentials), + "KinesisKCLConsumer") + .withRegionName(Regions.EU_CENTRAL_1.getName()); + + new Worker.Builder() + .recordProcessorFactory(new IpProcessorFactory()) + .config(consumerConfig) + .build() + .run(); + } + +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKPLApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKPLApplication.java new file mode 100644 index 0000000000..4ff7cf8087 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/kclkpl/KinesisKPLApplication.java @@ -0,0 +1,38 @@ +package com.baeldung.kclkpl; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.kinesis.producer.KinesisProducer; +import com.amazonaws.services.kinesis.producer.KinesisProducerConfiguration; + +@SpringBootApplication +public class KinesisKPLApplication { + + @Value("${aws.access.key}") + private String accessKey; + + @Value("${aws.secret.key}") + private String secretKey; + + public static void main(String[] args) { + SpringApplication.run(KinesisKPLApplication.class, args); + } + + @Bean + public KinesisProducer kinesisProducer() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + KinesisProducerConfiguration producerConfig = new KinesisProducerConfiguration() + .setCredentialsProvider(new AWSStaticCredentialsProvider(awsCredentials)) + .setVerifyCertificate(false) + .setRegion(Regions.EU_CENTRAL_1.getName()); + + return new KinesisProducer(producerConfig); + } + +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpConsumer.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ConsumerSDK.java similarity index 82% rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpConsumer.java rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ConsumerSDK.java index 949787b687..d95d66b75a 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpConsumer.java +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ConsumerSDK.java @@ -1,12 +1,9 @@ -package com.baeldung; +package com.baeldung.sdk; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.messaging.Sink; import org.springframework.stereotype.Component; import com.amazonaws.services.kinesis.AmazonKinesis; @@ -17,8 +14,7 @@ import com.amazonaws.services.kinesis.model.GetShardIteratorResult; import com.amazonaws.services.kinesis.model.ShardIteratorType; @Component -@EnableBinding(Sink.class) -public class IpConsumer { +public class ConsumerSDK { @Value("${ips.stream}") private String IPS_STREAM; @@ -31,12 +27,7 @@ public class IpConsumer { private GetShardIteratorResult shardIterator; - @StreamListener(Sink.INPUT) - public void consume(String ip) { - System.out.println(ip); - } - - private void consumeWithKinesis() { + public void consumeWithKinesis() { GetRecordsRequest recordsRequest = new GetRecordsRequest(); recordsRequest.setShardIterator(shardIterator.getShardIterator()); recordsRequest.setLimit(25); diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/KinesisSDKApplication.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/KinesisSDKApplication.java new file mode 100644 index 0000000000..28901c0723 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/KinesisSDKApplication.java @@ -0,0 +1,35 @@ +package com.baeldung.sdk; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.kinesis.AmazonKinesis; +import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; + +@SpringBootApplication +public class KinesisSDKApplication { + + @Value("${aws.access.key}") + private String accessKey; + + @Value("${aws.secret.key}") + private String secretKey; + + public static void main(String[] args) { + SpringApplication.run(KinesisSDKApplication.class, args); + } + + @Bean + public AmazonKinesis buildAmazonKinesis() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonKinesisClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .withRegion(Regions.EU_CENTRAL_1) + .build(); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProducer.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ProducerSDK.java similarity index 72% rename from spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProducer.java rename to spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ProducerSDK.java index f59b2161f9..76ece8ddb7 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/IpProducer.java +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/java/com/baeldung/sdk/ProducerSDK.java @@ -1,4 +1,4 @@ -package com.baeldung; +package com.baeldung.sdk; import java.nio.ByteBuffer; import java.util.List; @@ -7,9 +7,6 @@ import java.util.stream.IntStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.messaging.Source; -import org.springframework.messaging.support.MessageBuilder; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -18,8 +15,7 @@ import com.amazonaws.services.kinesis.model.PutRecordsRequest; import com.amazonaws.services.kinesis.model.PutRecordsRequestEntry; @Component -@EnableBinding(Source.class) -public class IpProducer { +public class ProducerSDK { @Value("${ips.partition.key}") private String IPS_PARTITION_KEY; @@ -27,17 +23,9 @@ public class IpProducer { @Value("${ips.stream}") private String IPS_STREAM; - @Autowired - private Source source; @Autowired private AmazonKinesis kinesis; - @Scheduled(fixedDelay = 3000L) - private void produce() { - IntStream.range(1, 200).mapToObj(ipSuffix -> "192.168.0." + ipSuffix) - .forEach(entry -> source.output().send(MessageBuilder.withPayload(entry).build())); - } - @Scheduled(fixedDelay = 3000L) private void produceWithKinesis() { List entries = IntStream.range(1, 200).mapToObj(ipSuffix -> { diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties index 1943766c26..777abef1cc 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/main/resources/application.properties @@ -1,6 +1,12 @@ +# configurations for AWS SDK consumer and producer aws.access.key=my-aws-access-key-goes-here aws.secret.key=my-aws-secret-key-goes-here +ips.partition.key=ips-partition-key +ips.stream=ips-stream +ips.shard.id=1 + +# configurations for Spring Cloud Stream Kineses Binder consumer and producer cloud.aws.credentials.access-key=my-aws-access-key cloud.aws.credentials.secret-key=my-aws-secret-key cloud.aws.region.static=eu-central-1 @@ -11,8 +17,4 @@ spring.cloud.stream.bindings.input.group=live-ips-group spring.cloud.stream.bindings.input.content-type=text/plain spring.cloud.stream.bindings.output.destination=myStream -spring.cloud.stream.bindings.output.content-type=text/plain - -ips.partition.key=ips-partition-key -ips.stream=ips-stream -ips.shard.id=1 \ No newline at end of file +spring.cloud.stream.bindings.output.content-type=text/plain \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java index a232d29be5..bbe871ea11 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/java/com/baeldung/KinesisApplicationManualTest.java @@ -5,11 +5,13 @@ import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import com.baeldung.kclkpl.KinesisKPLApplication; + /** * Manual Test - this test needs correct AWS Access Key and Secret to build the Amazon Kinesis and complete successfully */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = KinesisApplication.class) +@SpringBootTest(classes = KinesisKPLApplication.class) public class KinesisApplicationManualTest { @Test public void whenSpringContextIsBootstrapped_thenNoExceptions() { diff --git a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties index 1943766c26..777abef1cc 100644 --- a/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties +++ b/spring-cloud-modules/spring-cloud-stream/spring-cloud-stream-kinesis/src/test/resources/application.properties @@ -1,6 +1,12 @@ +# configurations for AWS SDK consumer and producer aws.access.key=my-aws-access-key-goes-here aws.secret.key=my-aws-secret-key-goes-here +ips.partition.key=ips-partition-key +ips.stream=ips-stream +ips.shard.id=1 + +# configurations for Spring Cloud Stream Kineses Binder consumer and producer cloud.aws.credentials.access-key=my-aws-access-key cloud.aws.credentials.secret-key=my-aws-secret-key cloud.aws.region.static=eu-central-1 @@ -11,8 +17,4 @@ spring.cloud.stream.bindings.input.group=live-ips-group spring.cloud.stream.bindings.input.content-type=text/plain spring.cloud.stream.bindings.output.destination=myStream -spring.cloud.stream.bindings.output.content-type=text/plain - -ips.partition.key=ips-partition-key -ips.stream=ips-stream -ips.shard.id=1 \ No newline at end of file +spring.cloud.stream.bindings.output.content-type=text/plain \ No newline at end of file diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java index e8aa63a88d..463d3209ea 100644 --- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java @@ -1,5 +1,6 @@ package com.baeldung.spring.kafka; +import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.Map; @@ -8,15 +9,20 @@ import org.apache.kafka.common.serialization.StringDeserializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.listener.DefaultErrorHandler; import org.springframework.kafka.support.converter.RecordMessageConverter; import org.springframework.kafka.support.converter.StringJsonMessageConverter; import org.springframework.kafka.support.mapping.DefaultJackson2JavaTypeMapper; import org.springframework.kafka.support.mapping.Jackson2JavaTypeMapper; import org.springframework.kafka.support.serializer.JsonDeserializer; +import org.springframework.util.backoff.BackOff; +import org.springframework.util.backoff.FixedBackOff; @EnableKafka @Configuration @@ -25,6 +31,12 @@ public class KafkaConsumerConfig { @Value(value = "${spring.kafka.bootstrap-servers}") private String bootstrapAddress; + @Value(value = "${kafka.backoff.interval}") + private Long interval; + + @Value(value = "${kafka.backoff.max_failure}") + private Long maxAttempts; + public ConsumerFactory consumerFactory(String groupId) { Map props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); @@ -71,7 +83,7 @@ public class KafkaConsumerConfig { public ConcurrentKafkaListenerContainerFactory filterKafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory factory = kafkaListenerContainerFactory("filter"); factory.setRecordFilterStrategy(record -> record.value() - .contains("World")); + .contains("World")); return factory; } @@ -83,7 +95,7 @@ public class KafkaConsumerConfig { } @Bean - public ConcurrentKafkaListenerContainerFactory greetingKafkaListenerContainerFactory() { + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(greetingConsumerFactory()); return factory; @@ -109,15 +121,32 @@ public class KafkaConsumerConfig { props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "group_id_test"); return new DefaultKafkaConsumerFactory<>(props); } @Bean - public ConcurrentKafkaListenerContainerFactory multiTypeKafkaListenerContainerFactory() { + @Primary + public ConcurrentKafkaListenerContainerFactory greetingKafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(multiTypeConsumerFactory()); factory.setMessageConverter(multiTypeConverter()); + factory.setCommonErrorHandler(errorHandler()); + factory.getContainerProperties() + .setAckMode(ContainerProperties.AckMode.RECORD); return factory; } + @Bean + public DefaultErrorHandler errorHandler() { + BackOff fixedBackOff = new FixedBackOff(interval, maxAttempts); + DefaultErrorHandler errorHandler = new DefaultErrorHandler((consumerRecord, e) -> { + System.out.println(String.format("consumed record %s because this exception was thrown",consumerRecord.toString(),e.getClass().getName())); + }, fixedBackOff); + //Commented because of the test + //errorHandler.addRetryableExceptions(SocketTimeoutException.class,RuntimeException.class); + errorHandler.addNotRetryableExceptions(NullPointerException.class); + return errorHandler; + } + } diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java index 9afb5ff0b6..4b43c84f15 100644 --- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/MultiTypeKafkaListener.java @@ -2,6 +2,7 @@ package com.baeldung.spring.kafka; import org.springframework.kafka.annotation.KafkaHandler; import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.messaging.MessagingException; import org.springframework.stereotype.Component; @Component @@ -9,7 +10,12 @@ import org.springframework.stereotype.Component; public class MultiTypeKafkaListener { @KafkaHandler + //@RetryableTopic(backoff = @Backoff(value = 3000L), attempts = "5", autoCreateTopics = "false",include = SocketTimeoutException.class, exclude = NullPointerException.class) public void handleGreeting(Greeting greeting) { + if (greeting.getName() + .equalsIgnoreCase("test")) { + throw new MessagingException("test not allowed"); + } System.out.println("Greeting received: " + greeting); } diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/RetryableApplicationKafkaApp.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/RetryableApplicationKafkaApp.java new file mode 100644 index 0000000000..e43207829a --- /dev/null +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/RetryableApplicationKafkaApp.java @@ -0,0 +1,14 @@ +package com.baeldung.spring.kafka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +@SpringBootApplication +@Import(value = { KafkaTopicConfig.class, KafkaConsumerConfig.class, KafkaProducerConfig.class }) +public class RetryableApplicationKafkaApp { + + public static void main(String[] args) { + SpringApplication.run(RetryableApplicationKafkaApp.class, args); + } +} diff --git a/spring-kafka/src/main/resources/application.properties b/spring-kafka/src/main/resources/application.properties index c57537e2af..691b6f55b7 100644 --- a/spring-kafka/src/main/resources/application.properties +++ b/spring-kafka/src/main/resources/application.properties @@ -14,4 +14,7 @@ monitor.producer.simulate=true monitor.consumer.simulate=true monitor.kafka.consumer.groupid.simulate=baeldungGrpSimulate test.topic=testtopic1 +kafka.backoff.interval=9000 +kafka.backoff.max_failure=5 + diff --git a/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java b/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java index eebcf778be..030d166ca4 100644 --- a/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java +++ b/spring-kafka/src/test/java/com/baeldung/kafka/embedded/EmbeddedKafkaIntegrationTest.java @@ -1,7 +1,6 @@ package com.baeldung.kafka.embedded; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -16,6 +15,8 @@ import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.test.context.EmbeddedKafka; import org.springframework.test.annotation.DirtiesContext; +import com.fasterxml.jackson.databind.ObjectMapper; + @SpringBootTest @DirtiesContext @EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" }) @@ -33,6 +34,8 @@ class EmbeddedKafkaIntegrationTest { @Value("${test.topic}") private String topic; + private ObjectMapper objectMapper = new ObjectMapper(); + @BeforeEach void setup() { consumer.resetLatch(); @@ -44,7 +47,8 @@ class EmbeddedKafkaIntegrationTest { template.send(topic, data); - boolean messageConsumed = consumer.getLatch().await(10, TimeUnit.SECONDS); + boolean messageConsumed = consumer.getLatch() + .await(10, TimeUnit.SECONDS); assertTrue(messageConsumed); assertThat(consumer.getPayload(), containsString(data)); } @@ -55,7 +59,8 @@ class EmbeddedKafkaIntegrationTest { producer.send(topic, data); - boolean messageConsumed = consumer.getLatch().await(10, TimeUnit.SECONDS); + boolean messageConsumed = consumer.getLatch() + .await(10, TimeUnit.SECONDS); assertTrue(messageConsumed); assertThat(consumer.getPayload(), containsString(data)); } diff --git a/spring-kafka/src/test/java/com/baeldung/spring/kafka/KafkaRetryableIntegrationTest.java b/spring-kafka/src/test/java/com/baeldung/spring/kafka/KafkaRetryableIntegrationTest.java new file mode 100644 index 0000000000..029031923e --- /dev/null +++ b/spring-kafka/src/test/java/com/baeldung/spring/kafka/KafkaRetryableIntegrationTest.java @@ -0,0 +1,84 @@ +package com.baeldung.spring.kafka; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.config.KafkaListenerEndpointRegistry; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.AcknowledgingConsumerAwareMessageListener; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.context.EmbeddedKafka; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@SpringBootTest(classes = RetryableApplicationKafkaApp.class) +@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" }) +public class KafkaRetryableIntegrationTest { + @ClassRule + public static EmbeddedKafkaBroker embeddedKafka = new EmbeddedKafkaBroker(1, true, "multitype"); + + @Autowired + private KafkaListenerEndpointRegistry registry; + + @Autowired + private KafkaTemplate template; + + private ObjectMapper objectMapper = new ObjectMapper(); + + private static final String CONTAINER_GROUP = "multiGroup"; + + private static final String TOPIC = "topic"; + + @Before + public void setup() { + System.setProperty("spring.kafka.bootstrap-servers", embeddedKafka.getBrokersAsString()); + } + + @Test + public void givenEmbeddedKafkaBroker_whenSendingAWellFormedMessage_thenMessageIsConsumed() throws Exception { + ConcurrentMessageListenerContainer container = (ConcurrentMessageListenerContainer) registry.getListenerContainer(CONTAINER_GROUP); + container.stop(); + @SuppressWarnings("unchecked") AcknowledgingConsumerAwareMessageListener messageListener = (AcknowledgingConsumerAwareMessageListener) container.getContainerProperties() + .getMessageListener(); + CountDownLatch latch = new CountDownLatch(1); + container.getContainerProperties() + .setMessageListener((AcknowledgingConsumerAwareMessageListener) (data, acknowledgment, consumer) -> { + messageListener.onMessage(data, acknowledgment, consumer); + latch.countDown(); + }); + Greeting greeting = new Greeting("test1", "test2"); + container.start(); + template.send(TOPIC, objectMapper.writeValueAsString(greeting)); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + } + + @Test + public void givenEmbeddedKafkaBroker_whenSendingAMalFormedMessage_thenMessageIsConsumedAfterRetry() throws Exception { + ConcurrentMessageListenerContainer container = (ConcurrentMessageListenerContainer) registry.getListenerContainer(CONTAINER_GROUP); + container.stop(); + @SuppressWarnings("unchecked") AcknowledgingConsumerAwareMessageListener messageListener = (AcknowledgingConsumerAwareMessageListener) container.getContainerProperties() + .getMessageListener(); + CountDownLatch latch = new CountDownLatch(1); + container.getContainerProperties() + .setMessageListener((AcknowledgingConsumerAwareMessageListener) (data, acknowledgment, consumer) -> { + messageListener.onMessage(data, acknowledgment, consumer); + latch.countDown(); + }); + container.start(); + Greeting greeting = new Greeting("test", "test"); + template.send(TOPIC, objectMapper.writeValueAsString(greeting)); + //this message will go on error + Greeting greeting2 = new Greeting("test2", "test2"); + template.send(TOPIC, objectMapper.writeValueAsString(greeting2)); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + } + +} diff --git a/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java b/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java index bf278cea90..58b80ff07e 100644 --- a/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java +++ b/testing-modules/testing-assertions/src/test/java/com/baeldung/listassert/OrderAgnosticListComparisonUnitTest.java @@ -51,4 +51,12 @@ public class OrderAgnosticListComparisonUnitTest { assertThat(a).hasSameElementsAs(b); } + + @Test + void whenTestingForOrderAgnosticEqualityWithDuplicateElementsBothList_ShouldBeEqual() { + List a = Arrays.asList("a", "a", "b", "c"); + List b = Arrays.asList("a", "b", "a", "c"); + + assertThat(a).containsExactlyInAnyOrderElementsOf(b); + } }