input) {
input.print();
diff --git a/aws/kinesis/src/main/resources/lambda_function/index.js b/aws/kinesis/src/main/resources/lambda_function/index.js
new file mode 100644
index 000000000..fde5c8ccd
--- /dev/null
+++ b/aws/kinesis/src/main/resources/lambda_function/index.js
@@ -0,0 +1,33 @@
+console.log('Loading function');
+
+const validateRecord = (recordElement)=>{
+ // record is considered valid if contains status field
+ return recordElement.includes("status")
+}
+
+exports.handler = async (event, context) => {
+ /* Process the list of records and transform them */
+ const output = event.records.map((record)=>{
+ const decodedData = Buffer.from(record.data, "base64").toString("utf-8")
+ let isValidRecord = validateRecord(decodedData)
+
+ if(isValidRecord){
+ let parsedRecord = JSON.parse(decodedData)
+ // read fields from parsed JSON for some more processing
+ const outputRecord = `status::${parsedRecord.status}`
+ return {
+ recordId: record.recordId,
+ result: 'Ok',
+ // payload is encoded back to base64 before returning the result
+ data: Buffer.from(outputRecord, "utf-8").toString("base64")
+ }
+
+ }else{
+ return {
+ recordId: record.recordId,
+ result: 'dropped',
+ data: record.data // payload is kept intact,
+ }
+ }
+ })
+};
diff --git a/aws/spring-cloud-aws-s3/.gitignore b/aws/spring-cloud-aws-s3/.gitignore
new file mode 100644
index 000000000..3f8f6096a
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/.gitignore
@@ -0,0 +1,26 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
diff --git a/aws/spring-cloud-aws-s3/.mvn/wrapper/maven-wrapper.jar b/aws/spring-cloud-aws-s3/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/aws/spring-cloud-aws-s3/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/spring-cloud-aws-s3/.mvn/wrapper/maven-wrapper.properties b/aws/spring-cloud-aws-s3/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..5f0536eb7
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/aws/spring-cloud-aws-s3/Dockerfile b/aws/spring-cloud-aws-s3/Dockerfile
new file mode 100644
index 000000000..ec285f483
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/Dockerfile
@@ -0,0 +1,15 @@
+FROM maven:3.9-amazoncorretto-21 as backend
+WORKDIR /backend
+COPY pom.xml .
+COPY lombok.config .
+RUN mvn dependency:go-offline -B
+COPY src ./src
+RUN mvn clean install -DskipITs
+RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
+
+FROM openjdk:21
+ARG DEPENDENCY=/backend/target/dependency
+COPY --from=backend ${DEPENDENCY}/BOOT-INF/lib /app/lib
+COPY --from=backend ${DEPENDENCY}/META-INF /app/META-INF
+COPY --from=backend ${DEPENDENCY}/BOOT-INF/classes /app
+ENTRYPOINT ["java", "-cp", "app:app/lib/*", "io.reflectoring.Application"]
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/README.md b/aws/spring-cloud-aws-s3/README.md
new file mode 100644
index 000000000..615f044fc
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/README.md
@@ -0,0 +1,26 @@
+## Interacting with Amazon S3 Bucket using Spring Cloud AWS
+
+Codebase demonstrating connection and interaction with provisioned Amazon S3 bucket using [Spring Cloud AWS](https://spring.io/projects/spring-cloud-aws).
+
+Contains integration tests to validate interaction between the application and Amazon S3 using [LocalStack](https://github.com/localstack/localstack) and [Testcontainers](https://github.com/testcontainers/testcontainers-java). Test cases can be executed with the command `./mvnw integration-test verify`.
+
+To run the application locally without provisioning actual AWS Resources, execute the below commands:
+
+```bash
+chmod +x localstack/init-s3-bucket.sh
+```
+
+```bash
+sudo docker-compose build
+```
+
+```bash
+sudo docker-compose up -d
+```
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Integrating Amazon S3 with Spring Boot Using Spring Cloud AWS](https://reflectoring.io/integrating-amazon-s3-with-spring-boot-using-spring-cloud-aws/)
+* [Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot](https://reflectoring.io/offloading-file-transfers-with-amazon-s3-presigned-urls-in-spring-boot/)
diff --git a/aws/spring-cloud-aws-s3/docker-compose.yml b/aws/spring-cloud-aws-s3/docker-compose.yml
new file mode 100644
index 000000000..a4248074e
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/docker-compose.yml
@@ -0,0 +1,37 @@
+version: '3.7'
+
+services:
+ localstack:
+ container_name: localstack
+ image: localstack/localstack:3.3
+ ports:
+ - 4566:4566
+ environment:
+ - SERVICES=s3
+ volumes:
+ - ./localstack/init-s3-bucket.sh:/etc/localstack/init/ready.d/init-s3-bucket.sh
+ networks:
+ - reflectoring
+
+ backend:
+ container_name: backend-application
+ build:
+ context: ./
+ dockerfile: Dockerfile
+ ports:
+ - 8080:8080
+ depends_on:
+ - localstack
+ environment:
+ spring.cloud.aws.s3.endpoint: 'http://localstack:4566'
+ spring.cloud.aws.s3.path-style-access-enabled: true
+ spring.cloud.aws.credentials.access-key: test
+ spring.cloud.aws.credentials.secret-key: test
+ spring.cloud.aws.s3.region: 'us-east-1'
+ io.reflectoring.aws.s3.bucket-name: 'reflectoring-bucket'
+ io.reflectoring.aws.s3.presigned-url.validity: 120
+ networks:
+ - reflectoring
+
+networks:
+ reflectoring:
diff --git a/aws/spring-cloud-aws-s3/localstack/init-s3-bucket.sh b/aws/spring-cloud-aws-s3/localstack/init-s3-bucket.sh
new file mode 100755
index 000000000..793209e5a
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/localstack/init-s3-bucket.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+bucket_name="reflectoring-bucket"
+
+awslocal s3api create-bucket --bucket $bucket_name
+
+echo "S3 bucket '$bucket_name' created successfully"
+echo "Executed init-s3-bucket.sh"
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/lombok.config b/aws/spring-cloud-aws-s3/lombok.config
new file mode 100644
index 000000000..a886d4642
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/lombok.config
@@ -0,0 +1 @@
+lombok.nonNull.exceptionType=IllegalArgumentException
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/mvnw b/aws/spring-cloud-aws-s3/mvnw
new file mode 100755
index 000000000..66df28542
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/spring-cloud-aws-s3/mvnw.cmd b/aws/spring-cloud-aws-s3/mvnw.cmd
new file mode 100644
index 000000000..95ba6f54a
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/aws/spring-cloud-aws-s3/pom.xml b/aws/spring-cloud-aws-s3/pom.xml
new file mode 100644
index 000000000..e1a9d5a35
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/pom.xml
@@ -0,0 +1,120 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.0
+
+
+
+ io.reflectoring
+ spring-cloud-aws-s3
+ 0.0.1
+
+ spring-cloud-aws-s3
+ Proof-of-concept demonstrating connection and interaction with provisioned S3 bucket using spring cloud aws. Contains integration tests to validate interaction between the application and AWS S3 using LocalStack and Testcontainers.
+
+
+ 21
+ 3.1.1
+
+
+
+
+ hardikSinghBehl
+ Hardik Singh Behl
+ behl.hardiksingh@gmail.com
+
+ Developer
+
+ UTC +5:30
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-s3
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ localstack
+ test
+
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws
+ ${spring.cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/Application.java b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/Application.java
new file mode 100644
index 000000000..7dd263b8d
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/Application.java
@@ -0,0 +1,13 @@
+package io.reflectoring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/configuration/AwsS3BucketProperties.java b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/configuration/AwsS3BucketProperties.java
new file mode 100644
index 000000000..2d5f577f0
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/configuration/AwsS3BucketProperties.java
@@ -0,0 +1,68 @@
+package io.reflectoring.configuration;
+
+import java.time.Duration;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import io.reflectoring.validation.BucketExists;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Maps configuration values defined in the active {@code .yml} file to the
+ * corresponding instance variables below. The configuration properties are
+ * referenced within the application to interact with the provisioned AWS S3
+ * Bucket.
+ *
+ *
+ * Example YAML configuration:
+ *
+ *
+ * io:
+ * reflectoring:
+ * aws:
+ * s3:
+ * bucket-name: s3-bucket-name
+ * presigned-url:
+ * validity: url-validity-in-seconds
+ *
+ */
+@Getter
+@Setter
+@Validated
+@ConfigurationProperties(prefix = "io.reflectoring.aws.s3")
+public class AwsS3BucketProperties {
+
+ @BucketExists
+ @NotBlank(message = "S3 bucket name must be configured")
+ private String bucketName;
+
+ @Valid
+ private PresignedUrl presignedUrl = new PresignedUrl();
+
+ @Getter
+ @Setter
+ @Validated
+ public class PresignedUrl {
+
+ /**
+ * The validity period in seconds for the generated presigned URLs. The
+ * URLs would not be accessible post expiration.
+ */
+ @NotNull(message = "S3 presigned URL validity must be specified")
+ @Positive(message = "S3 presigned URL validity must be a positive value")
+ private Integer validity;
+
+ }
+
+ public Duration getPresignedUrlValidity() {
+ var urlValidity = this.presignedUrl.validity;
+ return Duration.ofSeconds(urlValidity);
+ }
+
+}
diff --git a/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/service/StorageService.java b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/service/StorageService.java
new file mode 100644
index 000000000..69e8fe3d2
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/service/StorageService.java
@@ -0,0 +1,56 @@
+package io.reflectoring.service;
+
+import java.net.URL;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import io.awspring.cloud.s3.S3Resource;
+import io.awspring.cloud.s3.S3Template;
+import io.reflectoring.configuration.AwsS3BucketProperties;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+
+@Service
+@RequiredArgsConstructor
+@EnableConfigurationProperties(AwsS3BucketProperties.class)
+public class StorageService {
+
+ private final S3Template s3Template;
+ private final AwsS3BucketProperties awsS3BucketProperties;
+
+ @SneakyThrows
+ public void save(@NonNull final MultipartFile file) {
+ final var key = file.getOriginalFilename();
+ final var bucketName = awsS3BucketProperties.getBucketName();
+
+ s3Template.upload(bucketName, key, file.getInputStream());
+ }
+
+ public S3Resource retrieve(@NonNull final String objectKey) {
+ final var bucketName = awsS3BucketProperties.getBucketName();
+ return s3Template.download(bucketName, objectKey);
+ }
+
+ public void delete(@NonNull final String objectKey) {
+ final var bucketName = awsS3BucketProperties.getBucketName();
+ s3Template.deleteObject(bucketName, objectKey);
+ }
+
+ public URL generateViewablePresignedUrl(@NonNull final String objectKey) {
+ final var bucketName = awsS3BucketProperties.getBucketName();
+ final var urlValidity = awsS3BucketProperties.getPresignedUrlValidity();
+
+ return s3Template.createSignedGetURL(bucketName, objectKey, urlValidity);
+ }
+
+ public URL generateUploadablePresignedUrl(@NonNull final String objectKey) {
+ final var bucketName = awsS3BucketProperties.getBucketName();
+ final var urlValidity = awsS3BucketProperties.getPresignedUrlValidity();
+
+ return s3Template.createSignedPutURL(bucketName, objectKey, urlValidity);
+ }
+
+}
diff --git a/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/validation/BucketExistenceValidator.java b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/validation/BucketExistenceValidator.java
new file mode 100644
index 000000000..59a5d13bd
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/validation/BucketExistenceValidator.java
@@ -0,0 +1,18 @@
+package io.reflectoring.validation;
+
+import io.awspring.cloud.s3.S3Template;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class BucketExistenceValidator implements ConstraintValidator {
+
+ private final S3Template s3Template;
+
+ @Override
+ public boolean isValid(final String bucketName, final ConstraintValidatorContext context) {
+ return s3Template.bucketExists(bucketName);
+ }
+
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/validation/BucketExists.java b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/validation/BucketExists.java
new file mode 100644
index 000000000..1d48520f5
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/main/java/io/reflectoring/validation/BucketExists.java
@@ -0,0 +1,24 @@
+package io.reflectoring.validation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+@Documented
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = BucketExistenceValidator.class)
+public @interface BucketExists {
+
+ String message() default "No bucket exists with configured name.";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/src/main/resources/application.yaml b/aws/spring-cloud-aws-s3/src/main/resources/application.yaml
new file mode 100644
index 000000000..c94f4a895
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/main/resources/application.yaml
@@ -0,0 +1,16 @@
+spring:
+ cloud:
+ aws:
+ credentials:
+ access-key: ${AWS_ACCESS_KEY}
+ secret-key: ${AWS_SECRET_KEY}
+ s3:
+ region: ${AWS_S3_REGION}
+
+io:
+ reflectoring:
+ aws:
+ s3:
+ bucket-name: ${AWS_S3_BUCKET_NAME}
+ presigned-url:
+ validity: ${AWS_S3_PRESIGNED_URL_VALIDITY}
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/helper/InitializeS3Bucket.java b/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/helper/InitializeS3Bucket.java
new file mode 100644
index 000000000..05c47884c
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/helper/InitializeS3Bucket.java
@@ -0,0 +1,14 @@
+package io.reflectoring.helper;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(S3BucketInitializer.class)
+public @interface InitializeS3Bucket {
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/helper/S3BucketInitializer.java b/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/helper/S3BucketInitializer.java
new file mode 100644
index 000000000..0d319776b
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/helper/S3BucketInitializer.java
@@ -0,0 +1,56 @@
+package io.reflectoring.helper;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.containers.localstack.LocalStackContainer.Service;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class S3BucketInitializer implements BeforeAllCallback {
+
+ private static final DockerImageName LOCALSTACK_IMAGE = DockerImageName.parse("localstack/localstack:3.3");
+ private static final LocalStackContainer localStackContainer = new LocalStackContainer(LOCALSTACK_IMAGE)
+ .withCopyFileToContainer(MountableFile.forClasspathResource("init-s3-bucket.sh", 0744), "/etc/localstack/init/ready.d/init-s3-bucket.sh")
+ .withServices(Service.S3)
+ .waitingFor(Wait.forLogMessage(".*Executed init-s3-bucket.sh.*", 1));
+
+ // Bucket name as configured in src/test/resources/init-s3-bucket.sh
+ private static final String BUCKET_NAME = "reflectoring-bucket";
+ private static final Integer PRESIGNED_URL_VALIDITY = randomValiditySeconds();
+
+ @Override
+ public void beforeAll(final ExtensionContext context) {
+ log.info("Creating localstack container : {}", LOCALSTACK_IMAGE);
+
+ localStackContainer.start();
+ addConfigurationProperties();
+
+ log.info("Successfully started localstack container : {}", LOCALSTACK_IMAGE);
+ }
+
+ private void addConfigurationProperties() {
+ System.setProperty("spring.cloud.aws.credentials.access-key", localStackContainer.getAccessKey());
+ System.setProperty("spring.cloud.aws.credentials.secret-key", localStackContainer.getSecretKey());
+ System.setProperty("spring.cloud.aws.s3.region", localStackContainer.getRegion());
+ System.setProperty("spring.cloud.aws.s3.endpoint", localStackContainer.getEndpoint().toString());
+
+ System.setProperty("io.reflectoring.aws.s3.bucket-name", BUCKET_NAME);
+ System.setProperty("io.reflectoring.aws.s3.presigned-url.validity", String.valueOf(PRESIGNED_URL_VALIDITY));
+ }
+
+ private static int randomValiditySeconds() {
+ return ThreadLocalRandom.current().nextInt(5, 11);
+ }
+
+ public static String bucketName() {
+ return BUCKET_NAME;
+ }
+
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/service/StorageServiceIT.java b/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/service/StorageServiceIT.java
new file mode 100644
index 000000000..0543ccdf0
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/test/java/io/reflectoring/service/StorageServiceIT.java
@@ -0,0 +1,205 @@
+package io.reflectoring.service;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.multipart.MultipartFile;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.containers.localstack.LocalStackContainer.Service;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import io.awspring.cloud.s3.S3Exception;
+import io.awspring.cloud.s3.S3Template;
+import io.reflectoring.configuration.AwsS3BucketProperties;
+import lombok.SneakyThrows;
+import net.bytebuddy.utility.RandomString;
+import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
+
+@SpringBootTest
+class StorageServiceIT {
+
+ @Autowired
+ private S3Template s3Template;
+
+ @Autowired
+ private StorageService storageService;
+
+ @Autowired
+ private AwsS3BucketProperties awsS3BucketProperties;
+
+ private static final LocalStackContainer localStackContainer;
+
+ // Bucket name as configured in src/test/resources/init-s3-bucket.sh
+ private static final String BUCKET_NAME = "reflectoring-bucket";
+ private static final Integer PRESIGNED_URL_VALIDITY = randomValiditySeconds();
+
+ static {
+ localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:3.4"))
+ .withCopyFileToContainer(MountableFile.forClasspathResource("init-s3-bucket.sh", 0744), "/etc/localstack/init/ready.d/init-s3-bucket.sh")
+ .withServices(Service.S3)
+ .waitingFor(Wait.forLogMessage(".*Executed init-s3-bucket.sh.*", 1));
+ localStackContainer.start();
+ }
+
+ @DynamicPropertySource
+ static void properties(DynamicPropertyRegistry registry) {
+ registry.add("spring.cloud.aws.credentials.access-key", localStackContainer::getAccessKey);
+ registry.add("spring.cloud.aws.credentials.secret-key", localStackContainer::getSecretKey);
+ registry.add("spring.cloud.aws.s3.region", localStackContainer::getRegion);
+ registry.add("spring.cloud.aws.s3.endpoint", localStackContainer::getEndpoint);
+
+ registry.add("io.reflectoring.aws.s3.bucket-name", () -> BUCKET_NAME);
+ registry.add("io.reflectoring.aws.s3.presigned-url.validity", () -> PRESIGNED_URL_VALIDITY);
+ }
+
+ @Test
+ void shouldSaveFileSuccessfullyToBucket() {
+ // Prepare test file to upload
+ final var key = RandomString.make(10) + ".txt";
+ final var fileContent = RandomString.make(50);
+ final var fileToUpload = createTextFile(key, fileContent);
+
+ // Invoke method under test
+ storageService.save(fileToUpload);
+
+ // Verify that the file is saved successfully in S3 bucket
+ final var isFileSaved = s3Template.objectExists(BUCKET_NAME, key);
+ assertThat(isFileSaved).isTrue();
+ }
+
+ @Test
+ void saveShouldThrowExceptionForNonExistBucket() {
+ // Prepare test file to upload
+ final var key = RandomString.make(10) + ".txt";
+ final var fileContent = RandomString.make(50);
+ final var fileToUpload = createTextFile(key, fileContent);
+
+ // Configure a non-existent bucket name
+ final var nonExistingBucketName = RandomString.make(20).toLowerCase();
+ awsS3BucketProperties.setBucketName(nonExistingBucketName);
+
+ // Invoke method under test and assert exception
+ final var exception = assertThrows(S3Exception.class, () -> storageService.save(fileToUpload));
+ assertThat(exception.getCause()).hasCauseInstanceOf(NoSuchBucketException.class);
+
+ // Reset the bucket name to the original value
+ awsS3BucketProperties.setBucketName(BUCKET_NAME);
+ }
+
+ @Test
+ @SneakyThrows
+ void shouldFetchSavedFileSuccessfullyFromBucketForValidKey() {
+ // Prepare test file and upload to S3 Bucket
+ final var key = RandomString.make(10) + ".txt";
+ final var fileContent = RandomString.make(50);
+ final var fileToUpload = createTextFile(key, fileContent);
+ storageService.save(fileToUpload);
+
+ // Invoke method under test
+ final var retrievedObject = storageService.retrieve(key);
+
+ // Read the retrieved content and assert integrity
+ final var retrievedContent = readFile(retrievedObject.getContentAsByteArray());
+ assertThat(retrievedContent).isEqualTo(fileContent);
+ }
+
+ @Test
+ void shouldDeleteFileFromBucketSuccessfully() {
+ // Prepare test file and upload to S3 Bucket
+ final var key = RandomString.make(10) + ".txt";
+ final var fileContent = RandomString.make(50);
+ final var fileToUpload = createTextFile(key, fileContent);
+ storageService.save(fileToUpload);
+
+ // Verify that the file is saved successfully in S3 bucket
+ var isFileSaved = s3Template.objectExists(BUCKET_NAME, key);
+ assertThat(isFileSaved).isTrue();
+
+ // Invoke method under test
+ storageService.delete(key);
+
+ // Verify that file is deleted from the S3 bucket
+ isFileSaved = s3Template.objectExists(BUCKET_NAME, key);
+ assertThat(isFileSaved).isFalse();
+ }
+
+ @Test
+ @SneakyThrows
+ void shouldGeneratePresignedUrlToFetchStoredObjectFromBucket() {
+ // Prepare test file and upload to S3 Bucket
+ final var key = RandomString.make(10) + ".txt";
+ final var fileContent = RandomString.make(50);
+ final var fileToUpload = createTextFile(key, fileContent);
+ storageService.save(fileToUpload);
+
+ // Invoke method under test
+ final var presignedUrl = storageService.generateViewablePresignedUrl(key);
+
+ // Perform a GET request to the presigned URL
+ final var restClient = RestClient.builder().build();
+ final var responseBody = restClient.method(HttpMethod.GET).uri(URI.create(presignedUrl.toExternalForm()))
+ .retrieve().body(byte[].class);
+
+ // verify the retrieved content matches the expected file content.
+ final var retrievedContent = new String(responseBody, StandardCharsets.UTF_8);
+ assertThat(fileContent).isEqualTo(retrievedContent);
+ }
+
+ @Test
+ @SneakyThrows
+ void shouldGeneratePresignedUrlForUploadingObjectToBucket() {
+ // Prepare test file to upload
+ final var key = RandomString.make(10) + ".txt";
+ final var fileContent = RandomString.make(50);
+ final var fileToUpload = createTextFile(key, fileContent);
+
+ // Invoke method under test
+ final var presignedUrl = storageService.generateUploadablePresignedUrl(key);
+
+ // Upload the test file using the presigned URL
+ final var restClient = RestClient.builder().build();
+ final var response = restClient.method(HttpMethod.PUT).uri(URI.create(presignedUrl.toExternalForm()))
+ .body(fileToUpload.getBytes()).retrieve().toBodilessEntity();
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+ // Verify that the file is saved successfully in S3 bucket
+ var isFileSaved = s3Template.objectExists(BUCKET_NAME, key);
+ assertThat(isFileSaved).isTrue();
+ }
+
+ private String readFile(byte[] bytes) {
+ final var inputStreamReader = new InputStreamReader(new ByteArrayInputStream(bytes));
+ return new BufferedReader(inputStreamReader).lines().collect(Collectors.joining("\n"));
+ }
+
+ @SneakyThrows
+ private MultipartFile createTextFile(final String fileName, final String content) {
+ final var fileContentBytes = content.getBytes();
+ final var inputStream = new ByteArrayInputStream(fileContentBytes);
+ return new MockMultipartFile(fileName, fileName, "text/plain", inputStream);
+ }
+
+ private static int randomValiditySeconds() {
+ return ThreadLocalRandom.current().nextInt(5, 11);
+ }
+
+}
diff --git a/aws/spring-cloud-aws-s3/src/test/resources/init-s3-bucket.sh b/aws/spring-cloud-aws-s3/src/test/resources/init-s3-bucket.sh
new file mode 100644
index 000000000..793209e5a
--- /dev/null
+++ b/aws/spring-cloud-aws-s3/src/test/resources/init-s3-bucket.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+bucket_name="reflectoring-bucket"
+
+awslocal s3api create-bucket --bucket $bucket_name
+
+echo "S3 bucket '$bucket_name' created successfully"
+echo "Executed init-s3-bucket.sh"
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/.gitignore b/aws/spring-cloud-sns-sqs-pubsub/.gitignore
new file mode 100644
index 000000000..aa1ef7223
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/.gitignore
@@ -0,0 +1,4 @@
+target
+.project
+.settings
+.DS_Store
diff --git a/aws/spring-cloud-sns-sqs-pubsub/.mvn/wrapper/maven-wrapper.jar b/aws/spring-cloud-sns-sqs-pubsub/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/aws/spring-cloud-sns-sqs-pubsub/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/spring-cloud-sns-sqs-pubsub/.mvn/wrapper/maven-wrapper.properties b/aws/spring-cloud-sns-sqs-pubsub/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..5f0536eb7
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/aws/spring-cloud-sns-sqs-pubsub/README.md b/aws/spring-cloud-sns-sqs-pubsub/README.md
new file mode 100644
index 000000000..3ad27502e
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/README.md
@@ -0,0 +1,18 @@
+## Publisher-Subscriber Pattern using AWS SNS and SQS in Spring Boot
+
+Codebase demonstrating the implementation of publisher-subscriber pattern using AWS SNS and SQS in Spring Boot. [Spring Cloud AWS](https://spring.io/projects/spring-cloud-aws) is used to interact with AWS services in context.
+
+[LocalStack](https://github.com/localstack/localstack) has been used to containerize the multi-module Maven project for local development. The below commands can be used to start the applications:
+
+```bash
+./mvnw clean package spring-boot:build-image
+```
+```bash
+sudo docker-compose up -d
+```
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Publisher-Subscriber Pattern using AWS SNS and SQS in Spring Boot](https://reflectoring.io/publisher-subscriber-pattern-using-aws-sns-and-sqs-in-spring-boot)
diff --git a/aws/spring-cloud-sns-sqs-pubsub/docker-compose.yml b/aws/spring-cloud-sns-sqs-pubsub/docker-compose.yml
new file mode 100644
index 000000000..bae6c8451
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/docker-compose.yml
@@ -0,0 +1,52 @@
+version: '3.7'
+
+services:
+ localstack:
+ container_name: localstack
+ image: localstack/localstack:3.3
+ ports:
+ - 4566:4566
+ environment:
+ - SERVICES=sns,sqs
+ volumes:
+ - ./localstack/init-sns-topic.sh:/etc/localstack/init/ready.d/init-sns-topic.sh
+ - ./localstack/init-sqs-queue.sh:/etc/localstack/init/ready.d/init-sqs-queue.sh
+ - ./localstack/subscribe-sqs-to-sns.sh:/etc/localstack/init/ready.d/subscribe-sqs-to-sns.sh
+ networks:
+ - reflectoring
+
+ user-management-service:
+ container_name: user-management-service
+ image: aws-pubsub-user-management-service
+ ports:
+ - 8080:8080
+ depends_on:
+ - localstack
+ environment:
+ spring.cloud.aws.sns.endpoint: 'http://localstack:4566'
+ spring.cloud.aws.credentials.access-key: test
+ spring.cloud.aws.credentials.secret-key: test
+ spring.cloud.aws.sns.region: 'us-east-1'
+ io.reflectoring.aws.sns.topic-arn: 'arn:aws:sns:us-east-1:000000000000:user-account-created'
+ networks:
+ - reflectoring
+
+ notification-dispatcher-service:
+ container_name: notification-dispatcher-service
+ image: aws-pubsub-notification-dispatcher-service
+ ports:
+ - 9090:8080
+ depends_on:
+ - localstack
+ - user-management-service
+ environment:
+ spring.cloud.aws.sqs.endpoint: 'http://localstack:4566'
+ spring.cloud.aws.credentials.access-key: test
+ spring.cloud.aws.credentials.secret-key: test
+ spring.cloud.aws.sqs.region: 'us-east-1'
+ io.reflectoring.aws.sqs.queue-url: 'http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/dispatch-email-notification'
+ networks:
+ - reflectoring
+
+networks:
+ reflectoring:
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/.gitignore b/aws/spring-cloud-sns-sqs-pubsub/integration-test/.gitignore
new file mode 100644
index 000000000..549e00a2a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/.mvn/wrapper/maven-wrapper.jar b/aws/spring-cloud-sns-sqs-pubsub/integration-test/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/aws/spring-cloud-sns-sqs-pubsub/integration-test/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/.mvn/wrapper/maven-wrapper.properties b/aws/spring-cloud-sns-sqs-pubsub/integration-test/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..5f0536eb7
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/mvnw b/aws/spring-cloud-sns-sqs-pubsub/integration-test/mvnw
new file mode 100755
index 000000000..66df28542
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/mvnw.cmd b/aws/spring-cloud-sns-sqs-pubsub/integration-test/mvnw.cmd
new file mode 100644
index 000000000..95ba6f54a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/pom.xml b/aws/spring-cloud-sns-sqs-pubsub/integration-test/pom.xml
new file mode 100644
index 000000000..4057f5551
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+ 4.0.0
+
+
+ io.reflectoring
+ aws-pubsub
+ 0.0.1
+ ../pom.xml
+
+
+ integration-test
+ integration-test
+ Integration testing the publisher subscriber functionality of this proof-of-concept
+
+
+ 21
+
+
+
+
+ io.reflectoring
+ user-management-service
+ 0.0.1
+
+
+ io.reflectoring
+ notification-dispatcher-service
+ 0.0.1
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+ org.testcontainers
+ localstack
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/main/java/io/reflectoring/Application.java b/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/main/java/io/reflectoring/Application.java
new file mode 100644
index 000000000..7dd263b8d
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/main/java/io/reflectoring/Application.java
@@ -0,0 +1,13 @@
+package io.reflectoring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/test/java/io/reflectoring/PubSubIT.java b/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/test/java/io/reflectoring/PubSubIT.java
new file mode 100644
index 000000000..5d71cc888
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/test/java/io/reflectoring/PubSubIT.java
@@ -0,0 +1,96 @@
+package io.reflectoring;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.concurrent.TimeUnit;
+
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+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.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.web.servlet.MockMvc;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.containers.localstack.LocalStackContainer.Service;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import lombok.SneakyThrows;
+import net.bytebuddy.utility.RandomString;
+
+@AutoConfigureMockMvc
+@ExtendWith(OutputCaptureExtension.class)
+@SpringBootTest(classes = Application.class)
+class PubSubIT {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private static final LocalStackContainer localStackContainer;
+
+ // as configured in initializing hook script 'provision-resources.sh' in src/test/resources
+ private static final String TOPIC_ARN = "arn:aws:sns:us-east-1:000000000000:user-account-created";
+ private static final String QUEUE_URL = "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/dispatch-email-notification";
+
+ static {
+ localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:3.4"))
+ .withCopyFileToContainer(MountableFile.forClasspathResource("provision-resources.sh", 0744), "/etc/localstack/init/ready.d/provision-resources.sh")
+ .withServices(Service.SNS, Service.SQS)
+ .waitingFor(Wait.forLogMessage(".*Successfully provisioned resources.*", 1));
+ localStackContainer.start();
+ }
+
+ @DynamicPropertySource
+ static void properties(DynamicPropertyRegistry registry) {
+ registry.add("spring.cloud.aws.credentials.access-key", localStackContainer::getAccessKey);
+ registry.add("spring.cloud.aws.credentials.secret-key", localStackContainer::getSecretKey);
+
+ registry.add("spring.cloud.aws.sns.region", localStackContainer::getRegion);
+ registry.add("spring.cloud.aws.sns.endpoint", localStackContainer::getEndpoint);
+ registry.add("io.reflectoring.aws.sns.topic-arn", () -> TOPIC_ARN);
+
+ registry.add("spring.cloud.aws.sqs.region", localStackContainer::getRegion);
+ registry.add("spring.cloud.aws.sqs.endpoint", localStackContainer::getEndpoint);
+ registry.add("io.reflectoring.aws.sqs.queue-url", () -> QUEUE_URL);
+ }
+
+ @Test
+ @SneakyThrows
+ void test(CapturedOutput output) {
+ // prepare API request body to create user
+ final var name = RandomString.make();
+ final var emailId = RandomString.make() + "@reflectoring.io";
+ final var password = RandomString.make();
+ final var userCreationRequestBody = String.format("""
+ {
+ "name" : "%s",
+ "emailId" : "%s",
+ "password" : "%s"
+ }
+ """, name, emailId, password);
+
+ // execute API request to create user
+ final var userCreationApiPath = "/api/v1/users";
+ mockMvc.perform(post(userCreationApiPath)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(userCreationRequestBody))
+ .andExpect(status().isCreated());
+
+ // assert that message has been published to SNS topic
+ final var expectedPublisherLog = String.format("Successfully published message to topic ARN: %s", TOPIC_ARN);
+ Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> output.getAll().contains(expectedPublisherLog));
+
+ // assert that message has been received by the SQS queue
+ final var expectedSubscriberLog = String.format("Dispatching account creation email to %s on %s", name, emailId);
+ Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> output.getAll().contains(expectedSubscriberLog));
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/test/resources/provision-resources.sh b/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/test/resources/provision-resources.sh
new file mode 100644
index 000000000..121bf79c4
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/integration-test/src/test/resources/provision-resources.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+topic_name="user-account-created"
+queue_name="dispatch-email-notification"
+
+sns_arn_prefix="arn:aws:sns:us-east-1:000000000000"
+sqs_arn_prefix="arn:aws:sqs:us-east-1:000000000000"
+
+awslocal sns create-topic --name $topic_name
+echo "SNS topic '$topic_name' created successfully"
+
+awslocal sqs create-queue --queue-name $queue_name
+echo "SQS queue '$queue_name' created successfully"
+
+awslocal sns subscribe --topic-arn "$sns_arn_prefix:$topic_name" --protocol sqs --notification-endpoint "$sqs_arn_prefix:$queue_name"
+echo "Subscribed SQS queue '$queue_name' to SNS topic '$topic_name' successfully"
+
+echo "Successfully provisioned resources"
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/localstack/init-sns-topic.sh b/aws/spring-cloud-sns-sqs-pubsub/localstack/init-sns-topic.sh
new file mode 100755
index 000000000..14e480ced
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/localstack/init-sns-topic.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+topic_name="user-account-created"
+
+awslocal sns create-topic --name $topic_name
+
+echo "SNS topic '$topic_name' created successfully"
+echo "Executed init-sns-topic.sh"
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/localstack/init-sqs-queue.sh b/aws/spring-cloud-sns-sqs-pubsub/localstack/init-sqs-queue.sh
new file mode 100755
index 000000000..93dc9ec28
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/localstack/init-sqs-queue.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+queue_name="dispatch-email-notification"
+
+awslocal sqs create-queue --queue-name $queue_name
+
+echo "SQS queue '$queue_name' created successfully"
+echo "Executed init-sqs-queue.sh"
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/localstack/subscribe-sqs-to-sns.sh b/aws/spring-cloud-sns-sqs-pubsub/localstack/subscribe-sqs-to-sns.sh
new file mode 100755
index 000000000..5dff55b53
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/localstack/subscribe-sqs-to-sns.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+topic_name="user-account-created"
+queue_name="dispatch-email-notification"
+
+awslocal sns subscribe --topic-arn "arn:aws:sns:us-east-1:000000000000:$topic_name" --protocol sqs --notification-endpoint "arn:aws:sqs:us-east-1:000000000000:$queue_name"
+
+echo "Subscribed SQS queue '$queue_name' to SNS topic '$topic_name' successfully"
+echo "Executed subscribe-sqs-to-sns.sh"
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/mvnw b/aws/spring-cloud-sns-sqs-pubsub/mvnw
new file mode 100755
index 000000000..66df28542
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/spring-cloud-sns-sqs-pubsub/mvnw.cmd b/aws/spring-cloud-sns-sqs-pubsub/mvnw.cmd
new file mode 100644
index 000000000..95ba6f54a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.gitignore b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.gitignore
new file mode 100644
index 000000000..549e00a2a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.mvn/wrapper/maven-wrapper.jar b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.mvn/wrapper/maven-wrapper.properties b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..5f0536eb7
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/mvnw b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/mvnw
new file mode 100755
index 000000000..66df28542
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/mvnw.cmd b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/mvnw.cmd
new file mode 100644
index 000000000..95ba6f54a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/pom.xml b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/pom.xml
new file mode 100644
index 000000000..2e8e0a96c
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ 4.0.0
+
+
+ io.reflectoring
+ aws-pubsub
+ 0.0.1
+ ../pom.xml
+
+
+ notification-dispatcher-service
+ notification-dispatcher-service
+ Microservice acting as a subscriber to an AWS SQS queue
+
+
+ ${project.parent.artifactId}-${project.artifactId}
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-sqs
+
+
+
+
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/SubscriberApplication.java b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/SubscriberApplication.java
new file mode 100644
index 000000000..2df73612b
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/SubscriberApplication.java
@@ -0,0 +1,13 @@
+package io.reflectoring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SubscriberApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SubscriberApplication.class, args);
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/configuration/AwsSqsQueueProperties.java b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/configuration/AwsSqsQueueProperties.java
new file mode 100644
index 000000000..a6dc7df19
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/configuration/AwsSqsQueueProperties.java
@@ -0,0 +1,19 @@
+package io.reflectoring.configuration;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Validated
+@ConfigurationProperties(prefix = "io.reflectoring.aws.sqs")
+public class AwsSqsQueueProperties {
+
+ @NotBlank(message = "SQS queue URL must be configured")
+ private String queueUrl;
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/dto/UserCreatedEventDto.java b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/dto/UserCreatedEventDto.java
new file mode 100644
index 000000000..ebf3da74e
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/dto/UserCreatedEventDto.java
@@ -0,0 +1,13 @@
+package io.reflectoring.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class UserCreatedEventDto {
+
+ private String name;
+ private String emailId;
+
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/listener/EmailNotificationListener.java b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/listener/EmailNotificationListener.java
new file mode 100644
index 000000000..2865d72d7
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/java/io/reflectoring/listener/EmailNotificationListener.java
@@ -0,0 +1,22 @@
+package io.reflectoring.listener;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Component;
+import io.awspring.cloud.sqs.annotation.SqsListener;
+import io.awspring.cloud.sqs.annotation.SnsNotificationMessage;
+import io.reflectoring.configuration.AwsSqsQueueProperties;
+import io.reflectoring.dto.UserCreatedEventDto;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@EnableConfigurationProperties(AwsSqsQueueProperties.class)
+public class EmailNotificationListener {
+
+ @SqsListener("${io.reflectoring.aws.sqs.queue-url}")
+ public void listen(@SnsNotificationMessage final UserCreatedEventDto userCreatedEvent) {
+ log.info("Dispatching account creation email to {} on {}", userCreatedEvent.getName(), userCreatedEvent.getEmailId());
+ // business logic to send email
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/resources/application.yaml b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/resources/application.yaml
new file mode 100644
index 000000000..b42b5f5dd
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/notification-dispatcher-service/src/main/resources/application.yaml
@@ -0,0 +1,14 @@
+spring:
+ cloud:
+ aws:
+ credentials:
+ access-key: ${AWS_ACCESS_KEY}
+ secret-key: ${AWS_SECRET_KEY}
+ sqs:
+ region: ${AWS_SQS_REGION}
+
+io:
+ reflectoring:
+ aws:
+ sqs:
+ queue-url: ${AWS_SQS_QUEUE_URL}
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/pom.xml b/aws/spring-cloud-sns-sqs-pubsub/pom.xml
new file mode 100644
index 000000000..5b8e661ac
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/pom.xml
@@ -0,0 +1,103 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+
+ io.reflectoring
+ aws-pubsub
+ 0.0.1
+ pom
+
+
+ 21
+ 3.1.1
+
+
+
+
+ hardikSinghBehl
+ Hardik Singh Behl
+ behl.hardiksingh@gmail.com
+
+ Developer
+
+ UTC +5:30
+
+
+
+
+ user-management-service
+ notification-dispatcher-service
+ integration-test
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws
+ ${spring.cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ ${java.version}
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.gitignore b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.gitignore
new file mode 100644
index 000000000..549e00a2a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.mvn/wrapper/maven-wrapper.jar b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.mvn/wrapper/maven-wrapper.properties b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..5f0536eb7
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/lombok.config b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/lombok.config
new file mode 100644
index 000000000..a886d4642
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/lombok.config
@@ -0,0 +1 @@
+lombok.nonNull.exceptionType=IllegalArgumentException
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/mvnw b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/mvnw
new file mode 100755
index 000000000..66df28542
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/mvnw.cmd b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/mvnw.cmd
new file mode 100644
index 000000000..95ba6f54a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/pom.xml b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/pom.xml
new file mode 100644
index 000000000..f992bf553
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ 4.0.0
+
+
+ io.reflectoring
+ aws-pubsub
+ 0.0.1
+ ../pom.xml
+
+
+ user-management-service
+ user-management-service
+ Microservice acting as a publisher to an AWS SNS topic
+
+
+ ${project.parent.artifactId}-${project.artifactId}
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-sns
+
+
+
+
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/PublisherApplication.java b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/PublisherApplication.java
new file mode 100644
index 000000000..8fc70791a
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/PublisherApplication.java
@@ -0,0 +1,13 @@
+package io.reflectoring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class PublisherApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PublisherApplication.class, args);
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/configuration/AwsSnsTopicProperties.java b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/configuration/AwsSnsTopicProperties.java
new file mode 100644
index 000000000..70a9362d8
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/configuration/AwsSnsTopicProperties.java
@@ -0,0 +1,19 @@
+package io.reflectoring.configuration;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Validated
+@ConfigurationProperties(prefix = "io.reflectoring.aws.sns")
+public class AwsSnsTopicProperties {
+
+ @NotBlank(message = "SNS topic ARN must be configured")
+ private String topicArn;
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/controller/UserController.java b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/controller/UserController.java
new file mode 100644
index 000000000..6e7043343
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/controller/UserController.java
@@ -0,0 +1,29 @@
+package io.reflectoring.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.reflectoring.dto.UserCreationRequestDto;
+import io.reflectoring.service.UserService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/v1/users")
+public class UserController {
+
+ private final UserService userService;
+
+ @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity createUser(@Valid @RequestBody final UserCreationRequestDto userCreationRequest) {
+ userService.create(userCreationRequest);
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/dto/UserCreatedEventDto.java b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/dto/UserCreatedEventDto.java
new file mode 100644
index 000000000..ebf3da74e
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/dto/UserCreatedEventDto.java
@@ -0,0 +1,13 @@
+package io.reflectoring.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class UserCreatedEventDto {
+
+ private String name;
+ private String emailId;
+
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/dto/UserCreationRequestDto.java b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/dto/UserCreationRequestDto.java
new file mode 100644
index 000000000..a54729527
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/dto/UserCreationRequestDto.java
@@ -0,0 +1,20 @@
+package io.reflectoring.dto;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+
+@Getter
+public class UserCreationRequestDto {
+
+ @NotBlank(message = "Name must not be empty")
+ private String name;
+
+ @NotBlank(message = "EmailId must not be empty")
+ @Email(message = "EmailId must be of valid format")
+ private String emailId;
+
+ @NotBlank(message = "Password must not be empty")
+ private String password;
+
+}
\ No newline at end of file
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/service/UserService.java b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/service/UserService.java
new file mode 100644
index 000000000..5cc0cf017
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/java/io/reflectoring/service/UserService.java
@@ -0,0 +1,39 @@
+package io.reflectoring.service;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Service;
+
+import io.awspring.cloud.sns.core.SnsTemplate;
+import io.reflectoring.configuration.AwsSnsTopicProperties;
+import io.reflectoring.dto.UserCreatedEventDto;
+import io.reflectoring.dto.UserCreationRequestDto;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@EnableConfigurationProperties(AwsSnsTopicProperties.class)
+public class UserService {
+
+ private final SnsTemplate snsTemplate;
+ private final AwsSnsTopicProperties awsSnsTopicProperties;
+
+ public void create(@NonNull final UserCreationRequestDto userCreationRequest) {
+ // save user record in database
+
+ final var topicArn = awsSnsTopicProperties.getTopicArn();
+ final var payload = convert(userCreationRequest);
+ snsTemplate.convertAndSend(topicArn, payload);
+ log.info("Successfully published message to topic ARN: {}", topicArn);
+ }
+
+ private UserCreatedEventDto convert(@NonNull final UserCreationRequestDto userCreationRequest) {
+ final var userCreatedEvent = new UserCreatedEventDto();
+ userCreatedEvent.setName(userCreationRequest.getName());
+ userCreatedEvent.setEmailId(userCreationRequest.getEmailId());
+ return userCreatedEvent;
+ }
+
+}
diff --git a/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/resources/application.yaml b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/resources/application.yaml
new file mode 100644
index 000000000..00b74ea4f
--- /dev/null
+++ b/aws/spring-cloud-sns-sqs-pubsub/user-management-service/src/main/resources/application.yaml
@@ -0,0 +1,14 @@
+spring:
+ cloud:
+ aws:
+ credentials:
+ access-key: ${AWS_ACCESS_KEY}
+ secret-key: ${AWS_SECRET_KEY}
+ sns:
+ region: ${AWS_SNS_REGION}
+
+io:
+ reflectoring:
+ aws:
+ sns:
+ topic-arn: ${AWS_SNS_TOPIC_ARN}
\ No newline at end of file
diff --git a/aws/structured-logging-cw/.mvn/wrapper/maven-wrapper.jar b/aws/structured-logging-cw/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/aws/structured-logging-cw/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/structured-logging-cw/.mvn/wrapper/maven-wrapper.properties b/aws/structured-logging-cw/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..6f40a26ed
--- /dev/null
+++ b/aws/structured-logging-cw/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/aws/structured-logging-cw/README.md b/aws/structured-logging-cw/README.md
new file mode 100644
index 000000000..790b449d0
--- /dev/null
+++ b/aws/structured-logging-cw/README.md
@@ -0,0 +1,10 @@
+# Structured logging with Amazon CloudWatch
+
+Example code for producing Structured logs and ingesting and visualizing with Amazon CloudWatch.
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Structured Logging with Amazon CloudWatch](https://reflectoring.io/struct-log-with-cw/)
+
diff --git a/aws/structured-logging-cw/iac_aws/manage-VM/.terraform.lock.hcl b/aws/structured-logging-cw/iac_aws/manage-VM/.terraform.lock.hcl
new file mode 100644
index 000000000..93181f4c4
--- /dev/null
+++ b/aws/structured-logging-cw/iac_aws/manage-VM/.terraform.lock.hcl
@@ -0,0 +1,62 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+ version = "4.63.0"
+ hashes = [
+ "h1:dN7hK7srLB0ZScbsoqEP25/3yXX9kauF3elbEQ0yXFM=",
+ "zh:0162a9b61f45deed9fcc4a3c4a90341904b0c1c864b2226c8c6df14a87671d86",
+ "zh:230db13f43ced8e9dcb7966c32a2b11cff0708b639083cfc92bdb6cb92902c86",
+ "zh:2d630ef2ff0c5b6395799112d8101f75445e42e40cb55c7e280209310bdb5ce4",
+ "zh:34f7d6bee1e0be7cac99bd0812625a6a76823b0e59957e02120a3c27f847c2d8",
+ "zh:6137d3d63f03265fe0ab21b87c8f9fb9b5776780de9924107e21333ad347ae7b",
+ "zh:6d03651e7e2106f247a9e22883ec7f650b8a78202575fbc7409278ebe4278da4",
+ "zh:6eb7a55e6320c650aac3b3d9b973317ce29510d78b334294864d886ba468e4e6",
+ "zh:71d819f87edcb5345bc451a4079dda223e037bf0b960c10e65737ff4f925b2a1",
+ "zh:7e8792065385d6353e67905ae115e1dd30752c8310baa73c5100de4dedb78843",
+ "zh:8e761b2064a56b98c82bfe8fa4666837e7cfa953e0b91744b8609e88f94db7c0",
+ "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+ "zh:9ffb31588e06851e55618677b6c60f94399423e8c47fd43bab753700a4699a96",
+ "zh:e2417386f0ae3e7c44e789481f9054f68e590f8672bc667197a190d57b61b6f9",
+ "zh:e554812bff64e3c7e93839ec6905dbf696b9b1d5d8336e9c9fc69659ea4f39a0",
+ "zh:e61f064190045b5bd982fefa59de9f342fb07f8407d6cfa4aa39c370b93d2117",
+ ]
+}
+
+provider "registry.terraform.io/hashicorp/local" {
+ version = "2.4.0"
+ hashes = [
+ "h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=",
+ "zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9",
+ "zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf",
+ "zh:70a6f6a852dd83768d0778ce9817d81d4b3f073fab8fa570bff92dcb0824f732",
+ "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
+ "zh:82a803f2f484c8b766e2e9c32343e9c89b91997b9f8d2697f9f3837f62926b35",
+ "zh:9708a4e40d6cc4b8afd1352e5186e6e1502f6ae599867c120967aebe9d90ed04",
+ "zh:973f65ce0d67c585f4ec250c1e634c9b22d9c4288b484ee2a871d7fa1e317406",
+ "zh:c8fa0f98f9316e4cfef082aa9b785ba16e36ff754d6aba8b456dab9500e671c6",
+ "zh:cfa5342a5f5188b20db246c73ac823918c189468e1382cb3c48a9c0c08fc5bf7",
+ "zh:e0e2b477c7e899c63b06b38cd8684a893d834d6d0b5e9b033cedc06dd7ffe9e2",
+ "zh:f62d7d05ea1ee566f732505200ab38d94315a4add27947a60afa29860822d3fc",
+ "zh:fa7ce69dde358e172bd719014ad637634bbdabc49363104f4fca759b4b73f2ce",
+ ]
+}
+
+provider "registry.terraform.io/hashicorp/tls" {
+ version = "4.0.4"
+ hashes = [
+ "h1:GZcFizg5ZT2VrpwvxGBHQ/hO9r6g0vYdQqx3bFD3anY=",
+ "zh:23671ed83e1fcf79745534841e10291bbf34046b27d6e68a5d0aab77206f4a55",
+ "zh:45292421211ffd9e8e3eb3655677700e3c5047f71d8f7650d2ce30242335f848",
+ "zh:59fedb519f4433c0fdb1d58b27c210b27415fddd0cd73c5312530b4309c088be",
+ "zh:5a8eec2409a9ff7cd0758a9d818c74bcba92a240e6c5e54b99df68fff312bbd5",
+ "zh:5e6a4b39f3171f53292ab88058a59e64825f2b842760a4869e64dc1dc093d1fe",
+ "zh:810547d0bf9311d21c81cc306126d3547e7bd3f194fc295836acf164b9f8424e",
+ "zh:824a5f3617624243bed0259d7dd37d76017097dc3193dac669be342b90b2ab48",
+ "zh:9361ccc7048be5dcbc2fafe2d8216939765b3160bd52734f7a9fd917a39ecbd8",
+ "zh:aa02ea625aaf672e649296bce7580f62d724268189fe9ad7c1b36bb0fa12fa60",
+ "zh:c71b4cd40d6ec7815dfeefd57d88bc592c0c42f5e5858dcc88245d371b4b8b1e",
+ "zh:dabcd52f36b43d250a3d71ad7abfa07b5622c69068d989e60b79b2bb4f220316",
+ "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
+ ]
+}
diff --git a/aws/structured-logging-cw/iac_aws/manage-VM/cloudwatch-agent-config.json b/aws/structured-logging-cw/iac_aws/manage-VM/cloudwatch-agent-config.json
new file mode 100644
index 000000000..c9def1e06
--- /dev/null
+++ b/aws/structured-logging-cw/iac_aws/manage-VM/cloudwatch-agent-config.json
@@ -0,0 +1,121 @@
+{
+ "agent": {
+ "metrics_collection_interval": 10,
+ "logfile": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
+ },
+ "metrics": {
+ "metrics_collected": {
+ "cpu": {
+ "resources": [
+ "*"
+ ],
+ "measurement": [
+ {"name": "cpu_usage_idle", "rename": "CPU_USAGE_IDLE", "unit": "Percent"},
+ {"name": "cpu_usage_nice", "unit": "Percent"},
+ "cpu_usage_guest"
+ ],
+ "totalcpu": false,
+ "metrics_collection_interval": 10,
+ "append_dimensions": {
+ "customized_dimension_key_1": "customized_dimension_value_1",
+ "customized_dimension_key_2": "customized_dimension_value_2"
+ }
+ },
+ "disk": {
+ "resources": [
+ "/",
+ "/tmp"
+ ],
+ "measurement": [
+ {"name": "free", "rename": "DISK_FREE", "unit": "Gigabytes"},
+ "total",
+ "used"
+ ],
+ "ignore_file_system_types": [
+ "sysfs", "devtmpfs"
+ ],
+ "metrics_collection_interval": 60,
+ "append_dimensions": {
+ "customized_dimension_key_3": "customized_dimension_value_3",
+ "customized_dimension_key_4": "customized_dimension_value_4"
+ }
+ },
+ "diskio": {
+ "resources": [
+ "*"
+ ],
+ "measurement": [
+ "reads",
+ "writes",
+ "read_time",
+ "write_time",
+ "io_time"
+ ],
+ "metrics_collection_interval": 60
+ },
+ "swap": {
+ "measurement": [
+ "swap_used",
+ "swap_free",
+ "swap_used_percent"
+ ]
+ },
+ "mem": {
+ "measurement": [
+ "mem_used",
+ "mem_cached",
+ "mem_total"
+ ],
+ "metrics_collection_interval": 10
+ },
+ "net": {
+ "resources": [
+ "eth0"
+ ],
+ "measurement": [
+ "bytes_sent",
+ "bytes_recv",
+ "drop_in",
+ "drop_out"
+ ]
+ },
+ "netstat": {
+ "measurement": [
+ "tcp_established",
+ "tcp_syn_sent",
+ "tcp_close"
+ ],
+ "metrics_collection_interval": 60
+ },
+ "processes": {
+ "measurement": [
+ "running",
+ "sleeping",
+ "dead"
+ ]
+ }
+ },
+ "append_dimensions": {
+ "ImageId": "${aws:ImageId}",
+ "InstanceId": "${aws:InstanceId}",
+ "InstanceType": "${aws:InstanceType}",
+ "AutoScalingGroupName": "${aws:AutoScalingGroupName}"
+ },
+ "aggregation_dimensions" : [["ImageId"], ["InstanceId", "InstanceType"], ["d1"],[]]
+ },
+ "logs": {
+ "logs_collected": {
+ "files": {
+ "collect_list": [
+ {
+ "file_path": "/home/ec2-user/accountprocessor/logs/accountprocessor-logging-dev.log",
+ "log_group_name": "accountprocessor",
+ "log_stream_name": "{instance_id}_{hostname}",
+ "timezone": "Local"
+ }
+ ]
+ }
+ },
+ "log_stream_name": "/ec2/catchall"
+ }
+}
\ No newline at end of file
diff --git a/aws/structured-logging-cw/iac_aws/manage-VM/main.tf b/aws/structured-logging-cw/iac_aws/manage-VM/main.tf
new file mode 100644
index 000000000..3b055a0ed
--- /dev/null
+++ b/aws/structured-logging-cw/iac_aws/manage-VM/main.tf
@@ -0,0 +1,133 @@
+provider "aws" {
+ profile = "Admin-Account-Access-489455091964"
+ region = "eu-central-1"
+}
+
+data "aws_ami" "latest-linux" {
+ most_recent = true
+
+ filter {
+ name = "name"
+ values = ["*Linux 2023*"]
+ }
+
+ filter {
+ name = "virtualization-type"
+ values = ["hvm"]
+ }
+}
+
+resource "aws_key_pair" "tf-key-pair" {
+ key_name = "tf-key-pair"
+ public_key = tls_private_key.rsa.public_key_openssh
+}
+
+resource "tls_private_key" "rsa" {
+ algorithm = "RSA"
+ rsa_bits = 4096
+}
+
+resource "local_file" "tf-key" {
+ content = tls_private_key.rsa.private_key_pem
+ filename = "tf-key-pair.pem"
+}
+
+resource "aws_vpc_security_group_ingress_rule" "sg-ssh" {
+ security_group_id = aws_security_group.main.id
+
+ cidr_ipv4 = "0.0.0.0/0"
+ from_port = 22
+ ip_protocol = "tcp"
+ to_port = 22
+}
+
+resource "aws_vpc_security_group_egress_rule" "sg-down" {
+ security_group_id = aws_security_group.main.id
+
+ cidr_ipv4 = "0.0.0.0/0"
+ from_port = 443
+ ip_protocol = "tcp"
+ to_port = 443
+}
+
+resource "aws_cloudwatch_log_group" "accountsAppLogs" {
+ name = "accounts"
+
+ tags = {
+ Environment = "production"
+ Application = "accountsApi"
+ }
+}
+
+resource "aws_security_group" "main" {
+
+}
+
+resource "aws_instance" "ec2-web1" {
+ # ami = data.aws_ami.latest-linux.id
+ ami = "ami-0b7fd829e7758b06d"
+ instance_type = "t2.micro"
+ tags = {
+ Name = "app-ec2-server",
+ Created_By = "pratik"
+ }
+ key_name = "tf-key-pair"
+ vpc_security_group_ids = [aws_security_group.main.id]
+ user_data = <<-EOF
+ #!/bin/bash
+ echo "installing jdk"
+ yum update -y
+ wget https://download.java.net/java/GA/jdk20.0.1/b4887098932d415489976708ad6d1a4b/9/GPL/openjdk-20.0.1_linux-x64_bin.tar.gz
+ tar xvf openjdk*
+ export JAVA_HOME=jdk-20.0.1
+ export PATH=$JAVA_HOME/bin:$PATH
+ export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
+ sudo yum install amazon-cloudwatch-agent
+ EOF
+}
+
+resource "aws_iam_role" "ec2_role" {
+ name = "ec2_role"
+
+ # Terraform's "jsonencode" function converts a
+ # Terraform expression result to valid JSON syntax.
+ assume_role_policy = jsonencode({
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "cloudwatch:PutMetricData",
+ "ec2:DescribeVolumes",
+ "ec2:DescribeTags",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams",
+ "logs:DescribeLogGroups",
+ "logs:CreateLogStream",
+ "logs:CreateLogGroup"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "ssm:GetParameter"
+ ],
+ "Resource": "arn:aws:ssm:*:*:parameter/AmazonCloudWatch-*"
+ }
+ ]
+ })
+
+ tags = {
+ tag-key = "tag-value"
+ }
+}
+output "server_private_ip" {
+value = aws_instance.ec2-web1.private_ip
+}
+output "server_public_ipv4" {
+value = aws_instance.ec2-web1.public_ip
+}
+output "server_id" {
+value = aws_instance.ec2-web1.id
+}
diff --git a/aws/structured-logging-cw/mvnw b/aws/structured-logging-cw/mvnw
new file mode 100755
index 000000000..8d937f4c1
--- /dev/null
+++ b/aws/structured-logging-cw/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/structured-logging-cw/mvnw.cmd b/aws/structured-logging-cw/mvnw.cmd
new file mode 100644
index 000000000..f80fbad3e
--- /dev/null
+++ b/aws/structured-logging-cw/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/aws/structured-logging-cw/pom.xml b/aws/structured-logging-cw/pom.xml
new file mode 100644
index 000000000..1036729d2
--- /dev/null
+++ b/aws/structured-logging-cw/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.5
+
+
+ io.pratik
+ accountProcessor
+ 0.0.1-SNAPSHOT
+ accountProcessor
+ Sample project for Spring Boot
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.apache.logging.log4j
+ log4j-to-slf4j
+
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
+
diff --git a/aws/structured-logging-cw/src/main/java/io/pratik/AccountInquiryApplication.java b/aws/structured-logging-cw/src/main/java/io/pratik/AccountInquiryApplication.java
new file mode 100644
index 000000000..916f532dc
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/java/io/pratik/AccountInquiryApplication.java
@@ -0,0 +1,19 @@
+package io.pratik;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AccountInquiryApplication {
+ private static final Logger LOG = LogManager.getLogger(AccountInquiryApplication.class);
+
+ public static void main(String[] args) {
+ LOG.info("Starting application");
+
+ SpringApplication.run(AccountInquiryApplication.class, args);
+
+ }
+
+}
diff --git a/aws/structured-logging-cw/src/main/java/io/pratik/accountProcessor/AccountInquiryController.java b/aws/structured-logging-cw/src/main/java/io/pratik/accountProcessor/AccountInquiryController.java
new file mode 100644
index 000000000..86c1691e5
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/java/io/pratik/accountProcessor/AccountInquiryController.java
@@ -0,0 +1,33 @@
+package io.pratik.accountProcessor;
+
+import io.pratik.models.AccountDetail;
+import io.pratik.services.AccountService;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Optional;
+
+
+@RestController
+@RequestMapping("/accounts")
+public class AccountInquiryController {
+ private AccountService accountService;
+ private static final Logger LOG = LogManager.getLogger(AccountInquiryController.class);
+
+
+ public AccountInquiryController(final AccountService accountService){
+ this.accountService = accountService;
+ }
+ @GetMapping("/{accountNo}")
+ @ResponseBody
+ public AccountDetail getAccountDetails(@PathVariable("accountNo") String accountNo) {
+ ThreadContext.put("accountNo", accountNo);
+ LOG.info("fetching account details for account ");
+ Optional accountDetail = accountService.getAccount(accountNo);
+ LOG.info("Details of account {}", accountDetail);
+ ThreadContext.clearAll();
+ return accountDetail.orElse(AccountDetail.builder().build());
+ }
+}
diff --git a/aws/structured-logging-cw/src/main/java/io/pratik/models/AccountDetail.java b/aws/structured-logging-cw/src/main/java/io/pratik/models/AccountDetail.java
new file mode 100644
index 000000000..e50db51e9
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/java/io/pratik/models/AccountDetail.java
@@ -0,0 +1,15 @@
+package io.pratik.models;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class AccountDetail {
+ private String accountNo;
+ private Double balance;
+ private String currency;
+ private String openingDate;
+ private String accountHolder;
+
+}
diff --git a/aws/structured-logging-cw/src/main/java/io/pratik/services/AccountService.java b/aws/structured-logging-cw/src/main/java/io/pratik/services/AccountService.java
new file mode 100644
index 000000000..c9d07e465
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/java/io/pratik/services/AccountService.java
@@ -0,0 +1,28 @@
+package io.pratik.services;
+
+import io.pratik.models.AccountDetail;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class AccountService {
+ private static final Logger LOG = LogManager.getLogger(AccountService.class);
+
+ public Optional getAccount(final String accountNo) {
+ // simulating an account not exists scenario
+ if(accountNo.endsWith("000")){
+ LOG.error("Account not found:: {}", accountNo);
+ return Optional.empty();
+ }
+ return Optional.ofNullable(AccountDetail.builder().accountHolder("Jack Melon")
+ .accountNo("GWR" + accountNo)
+ .balance(19000.89)
+ .currency("USD")
+ .openingDate("12/01/2015")
+ .build());
+
+ }
+}
diff --git a/aws/structured-logging-cw/src/main/resources/application.properties b/aws/structured-logging-cw/src/main/resources/application.properties
new file mode 100644
index 000000000..257b30648
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.profiles.active=dev
\ No newline at end of file
diff --git a/aws/structured-logging-cw/src/main/resources/log4j2.xml b/aws/structured-logging-cw/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..418d02d79
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/resources/log4j2.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws/structured-logging-cw/src/main/resources/logger_layout.json b/aws/structured-logging-cw/src/main/resources/logger_layout.json
new file mode 100644
index 000000000..26fb93467
--- /dev/null
+++ b/aws/structured-logging-cw/src/main/resources/logger_layout.json
@@ -0,0 +1,44 @@
+{
+ "timestamp": {
+ "$resolver": "timestamp",
+ "pattern": {
+ "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ "timeZone": "UTC"
+ }
+ }, "level": {
+ "$resolver": "level",
+ "field": "name"
+},
+ "contextMap": {
+ "$resolver": "mdc",
+ "stringified": true
+ },
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "thrown": {
+ "message": {
+ "$resolver": "exception",
+ "field": "message"
+ },
+ "name": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "extendedStackTrace": {
+ "$resolver": "exception",
+ "field": "stackTrace"
+ }
+ },
+ "source": {
+ "class": {
+ "$resolver": "source",
+ "field": "className"
+ },
+ "line": {
+ "$resolver": "source",
+ "field": "lineNumber"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-all.sh b/build-all.sh
index 4fb61f623..3c7e03954 100755
--- a/build-all.sh
+++ b/build-all.sh
@@ -87,6 +87,13 @@ if [[ "$MODULE" == "module7" ]]
then
# ADD NEW MODULES HERE
# (add new modules above the rest so you get quicker feedback if it fails)
+ build maven_module "aws/spring-cloud-aws-s3"
+ build maven_module "aws/spring-cloud-sns-sqs-pubsub"
+ build maven_module "apache-http-client"
+ build maven_module "archunit"
+ build maven_module "aws/structured-logging-cw"
+ build_gradle_module "kotlin/coroutines"
+ build_maven_module "core-java/streams/data-streams"
build maven_module "aws/kinesis"
build maven_module "aws/sqs"
build_maven_module "core-java/annotation-processing/introduction-to-annotations"
@@ -100,6 +107,8 @@ fi
if [[ "$MODULE" == "module6" ]]
then
+ build maven_module "core-java/records"
+ build_maven_module "spring-boot/spring-boot-app-info"
build_maven_module "spring-boot/spring-boot-null-safe-annotations"
build maven_module "aws/cdkv2"
build_maven_module "immutables"
@@ -198,9 +207,12 @@ then
build_maven_module "resilience4j/timelimiter"
build_maven_module "resilience4j/bulkhead"
build_maven_module "resilience4j/circuitbreaker"
+ build_maven_module "openfeign/openfeign-client-intro"
build_gradle_module "spring-data/spring-data-jdbc-converter"
build_gradle_module "reactive"
build_gradle_module "junit/assumptions"
+ build_maven_module "junit/junit5/junit5"
+ build_maven_module "junit/junit5/functional-interfaces"
build_gradle_module "logging"
build_gradle_module "pact/pact-feign-consumer"
diff --git a/core-java/functional-programming/functional-interfaces/.gitignore b/core-java/functional-programming/functional-interfaces/.gitignore
new file mode 100644
index 000000000..3aff99610
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/.gitignore
@@ -0,0 +1,29 @@
+HELP.md
+target/*
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
\ No newline at end of file
diff --git a/core-java/functional-programming/functional-interfaces/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/functional-programming/functional-interfaces/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/core-java/functional-programming/functional-interfaces/.mvn/wrapper/maven-wrapper.jar b/core-java/functional-programming/functional-interfaces/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/functional-programming/functional-interfaces/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/functional-programming/functional-interfaces/.mvn/wrapper/maven-wrapper.properties b/core-java/functional-programming/functional-interfaces/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/core-java/functional-programming/functional-interfaces/README.md b/core-java/functional-programming/functional-interfaces/README.md
new file mode 100644
index 000000000..5247c8abc
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/README.md
@@ -0,0 +1,3 @@
+# Related Blog Posts
+
+* [One Stop Guide to Java Functional Interfaces](https://reflectoring.io/one-stop-guide-to-java-functional-interfaces/)
diff --git a/core-java/functional-programming/functional-interfaces/mvnw b/core-java/functional-programming/functional-interfaces/mvnw
new file mode 100644
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/core-java/functional-programming/functional-interfaces/mvnw.cmd b/core-java/functional-programming/functional-interfaces/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/core-java/functional-programming/functional-interfaces/pom.xml b/core-java/functional-programming/functional-interfaces/pom.xml
new file mode 100644
index 000000000..a871d179b
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/pom.xml
@@ -0,0 +1,85 @@
+
+
+
+ 4.0.0
+
+ io.reflectoring
+ functional-interfaces
+ 1.0-SNAPSHOT
+
+ Java Functional Interfaces
+ https://reflectoring.io
+
+
+ UTF-8
+ 17
+ 17
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.10.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.10.0
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.9
+ test
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.9
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.28
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.2
+
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 8.0.1.Final
+
+
+
+
+
+
+ org.junit
+ junit-bom
+ 5.10.0
+ pom
+ import
+
+
+
+
diff --git a/core-java/functional-programming/functional-interfaces/src/main/java/io/reflectoring/function/custom/ArithmeticOperation.java b/core-java/functional-programming/functional-interfaces/src/main/java/io/reflectoring/function/custom/ArithmeticOperation.java
new file mode 100644
index 000000000..5ca8c9164
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/main/java/io/reflectoring/function/custom/ArithmeticOperation.java
@@ -0,0 +1,13 @@
+package io.reflectoring.function.custom;
+
+/** The arithmetic operation functional interface. */
+public interface ArithmeticOperation {
+ /**
+ * Operates on two integer inputs to calculate a result.
+ *
+ * @param a the first number
+ * @param b the second number
+ * @return the result
+ */
+ int operate(int a, int b);
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/ConsumerTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/ConsumerTest.java
new file mode 100644
index 000000000..74737658a
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/ConsumerTest.java
@@ -0,0 +1,183 @@
+package io.reflectoring.function;
+
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.*;
+import java.util.stream.DoubleStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class ConsumerTest {
+ @Test
+ void consumer() {
+ Consumer> trim =
+ strings -> {
+ if (strings != null) {
+ strings.replaceAll(s -> s == null ? null : s.trim());
+ }
+ };
+ Consumer> upperCase =
+ strings -> {
+ if (strings != null) {
+ strings.replaceAll(s -> s == null ? null : s.toUpperCase());
+ }
+ };
+
+ List input = null;
+ input = Arrays.asList(null, "", " Joy", " Joy ", "Joy ", "Joy");
+ trim.accept(input);
+ Assertions.assertEquals(Arrays.asList(null, "", "Joy", "Joy", "Joy", "Joy"), input);
+
+ input = Arrays.asList(null, "", " Joy", " Joy ", "Joy ", "Joy");
+ trim.andThen(upperCase).accept(input);
+ Assertions.assertEquals(Arrays.asList(null, "", "JOY", "JOY", "JOY", "JOY"), input);
+ }
+
+ @Test
+ void biConsumer() {
+ BiConsumer, Double> discountRule =
+ (prices, discount) -> {
+ if (prices != null && discount != null) {
+ prices.replaceAll(price -> price * discount);
+ }
+ };
+ BiConsumer, Double> bulkDiscountRule =
+ (prices, discount) -> {
+ if (prices != null && discount != null && prices.size() > 2) {
+ // 20% discount cart has 2 items or more
+ prices.replaceAll(price -> price * 0.80);
+ }
+ };
+
+ double discount = 0.90; // 10% discount
+ List prices = null;
+ prices = Arrays.asList(20.0, 30.0, 100.0);
+ discountRule.accept(prices, discount);
+ Assertions.assertEquals(Arrays.asList(18.0, 27.0, 90.0), prices);
+
+ prices = Arrays.asList(20.0, 30.0, 100.0);
+ discountRule.andThen(bulkDiscountRule).accept(prices, discount);
+ Assertions.assertEquals(Arrays.asList(14.4, 21.6, 72.0), prices);
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "15,Turning off AC.",
+ "22,---",
+ "25,Turning on AC.",
+ "52,Alert! Temperature not safe for humans."
+ })
+ void intConsumer(int temperature, String expected) {
+ AtomicReference message = new AtomicReference<>();
+ IntConsumer temperatureSensor =
+ t -> {
+ message.set("---");
+ if (t <= 20) {
+ message.set("Turning off AC.");
+ } else if (t >= 24 && t <= 50) {
+ message.set("Turning on AC.");
+ } else if (t > 50) {
+ message.set("Alert! Temperature not safe for humans.");
+ }
+ };
+
+ temperatureSensor.accept(temperature);
+ Assertions.assertEquals(expected, message.toString());
+ }
+
+ @Test
+ void longConsumer() {
+ long duration = TimeUnit.MINUTES.toMillis(20);
+ long stopTime = Instant.now().toEpochMilli() + duration;
+ AtomicReference message = new AtomicReference<>();
+
+ LongConsumer timeCheck =
+ millis -> {
+ message.set("---");
+ if (millis >= stopTime) {
+ message.set("STOP");
+ } else {
+ message.set("CONTINUE");
+ }
+ };
+
+ // Current time in milliseconds
+ long currentTimeMillis = Instant.now().toEpochMilli();
+ timeCheck.accept(currentTimeMillis);
+ Assertions.assertEquals("CONTINUE", message.toString());
+
+ long pastStopTime = currentTimeMillis + duration + 10000L;
+ timeCheck.accept(pastStopTime);
+ Assertions.assertEquals("STOP", message.toString());
+ }
+
+ @Test
+ void doubleConsumer() {
+ AtomicReference temperature = new AtomicReference<>(0.0);
+ DoubleConsumer celsiusToFahrenheit = celsius -> temperature.set(celsius * 9 / 5 + 32);
+ celsiusToFahrenheit.accept(100);
+ Assertions.assertEquals(212.0, temperature.get());
+
+ // radius of circles
+ List input = Arrays.asList(1, 2, 3, 4, 5);
+ // calculate area of circle
+ BiConsumer biConsumer =
+ (radius, consumer) -> {
+ consumer.accept(Math.PI * radius * radius);
+ };
+ DoubleStream result = input.stream().mapMultiToDouble(biConsumer);
+ Assertions.assertArrayEquals(
+ new double[] {3.14, 12.56, 28.27, 50.26, 78.53}, result.toArray(), 0.01);
+ }
+
+ @Test
+ void objIntConsumer() {
+ AtomicReference result = new AtomicReference<>();
+ ObjIntConsumer trim =
+ (input, len) -> {
+ if (input != null && input.length() > len) {
+ result.set(input.substring(0, len));
+ }
+ };
+
+ trim.accept("123456789", 3);
+ Assertions.assertEquals("123", result.get());
+ }
+
+ @Test
+ void objLongConsumer() {
+ AtomicReference result = new AtomicReference<>();
+ ObjLongConsumer trim =
+ (input, delta) -> {
+ if (input != null) {
+ result.set(input.plusSeconds(delta));
+ }
+ };
+
+ LocalDateTime input = LocalDateTime.now().toLocalDate().atStartOfDay();
+ trim.accept(input, TimeUnit.DAYS.toMillis(1));
+ Assertions.assertEquals(0, result.get().getMinute());
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ value = {"{0};12,345.678", "{0,number,#.##};12345.68", "{0,number,currency};$12,345.68"},
+ delimiter = ';')
+ void objDoubleConsumer(String formatString, String expected) {
+ AtomicReference result = new AtomicReference<>();
+ ObjDoubleConsumer format =
+ (formatStr, input) -> {
+ result.set(MessageFormat.format(formatStr, input));
+ };
+
+ double number = 12345.678;
+ format.accept(formatString, number);
+ Assertions.assertEquals(expected, result.get());
+ }
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/FunctionTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/FunctionTest.java
new file mode 100644
index 000000000..abdb2eb79
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/FunctionTest.java
@@ -0,0 +1,194 @@
+package io.reflectoring.function;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Date;
+import java.util.function.*;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class FunctionTest {
+
+ @Test
+ void simpleFunction() {
+ Function toUpper = s -> s == null ? null : s.toUpperCase();
+ Assertions.assertEquals("JOY", toUpper.apply("joy"));
+ Assertions.assertNull(toUpper.apply(null));
+ }
+
+ @Test
+ void functionComposition() {
+ Function toUpper = s -> s == null ? null : s.toUpperCase();
+ Function replaceVowels =
+ s ->
+ s == null
+ ? null
+ : s.replace("A", "")
+ .replace("E", "")
+ .replace("I", "")
+ .replace("O", "")
+ .replace("U", "");
+ Assertions.assertEquals("APPLE", toUpper.compose(replaceVowels).apply("apple"));
+ Assertions.assertEquals("PPL", toUpper.andThen(replaceVowels).apply("apple"));
+ }
+
+ @Test
+ void biFunction() {
+ BiFunction bigger =
+ (first, second) -> first > second ? first : second;
+ Function square = number -> number * number;
+
+ Assertions.assertEquals(10, bigger.apply(4, 10));
+ Assertions.assertEquals(100, bigger.andThen(square).apply(4, 10));
+ }
+
+ @Test
+ void intFunction() {
+ IntFunction square = number -> number * number;
+ Assertions.assertEquals(100, square.apply(10));
+ }
+
+ @Test
+ void intToDoubleFunction() {
+ int principalAmount = 1000; // Initial investment amount
+ double interestRate = 0.05; // Annual accruedInterest rate (5%)
+
+ IntToDoubleFunction accruedInterest = principal -> principal * interestRate;
+ Assertions.assertEquals(50.0, accruedInterest.applyAsDouble(principalAmount));
+ }
+
+ @Test
+ void intToLongFunction() {
+ IntToLongFunction factorial =
+ n -> {
+ long result = 1L;
+ for (int i = 1; i <= n; i++) {
+ result *= i;
+ }
+ return result;
+ };
+ IntStream input = IntStream.range(1, 6);
+ final long[] result = input.mapToLong(factorial).toArray();
+ Assertions.assertArrayEquals(new long[] {1L, 2L, 6L, 24L, 120L}, result);
+ }
+
+ @Test
+ void longFunction() {
+ LongFunction squareArea = side -> (double) (side * side);
+ Assertions.assertEquals(400d, squareArea.apply(20L));
+ }
+
+ @Test
+ void longToDoubleFunction() {
+ LongToDoubleFunction squareArea = side -> (double) (side * side);
+ Assertions.assertEquals(400d, squareArea.applyAsDouble(20L));
+
+ LongStream input = LongStream.range(1L, 6L);
+ final double[] result = input.mapToDouble(squareArea).toArray();
+ Assertions.assertArrayEquals(new double[] {1.0, 4.0, 9.0, 16.0, 25.0}, result);
+ }
+
+ @Test
+ void longToIntFunction() {
+ LongToIntFunction digitCount = number -> String.valueOf(number).length();
+ LongStream input = LongStream.of(1L, 120, 15L, 12345L);
+ final int[] result = input.mapToInt(digitCount).toArray();
+ Assertions.assertArrayEquals(new int[] {1, 3, 2, 5}, result);
+ }
+
+ @Test
+ void doubleFunction() {
+ // grouping separator like a comma for thousands
+ // exactly two digits after the decimal point
+ DoubleFunction numberFormatter = number -> String.format("%1$,.2f", number);
+ Assertions.assertEquals("999,999.12", numberFormatter.apply(999999.123));
+ }
+
+ @Test
+ void doubleToIntFunction() {
+ DoubleToIntFunction wholeNumber = number -> Double.valueOf(number).intValue();
+ DoubleStream input = DoubleStream.of(1.0, 12.34, 99.0, 101.444);
+ int[] result = input.mapToInt(wholeNumber).toArray();
+ Assertions.assertArrayEquals(new int[] {1, 12, 99, 101}, result);
+ }
+
+ @Test
+ void doubleToLongFunction() {
+ DoubleToLongFunction celsiusToFahrenheit = celsius -> Math.round(celsius * 9 / 5 + 32);
+ DoubleStream input = DoubleStream.of(0.0, 25.0, 100.0);
+ long[] result = input.mapToLong(celsiusToFahrenheit).toArray();
+ Assertions.assertArrayEquals(new long[] {32, 77, 212}, result);
+ }
+
+ @Test
+ void toDoubleFunction() {
+ ToDoubleFunction fahrenheitToCelsius =
+ (fahrenheit) -> (double) ((fahrenheit - 32) * 5) / 9;
+ Assertions.assertEquals(0.0, fahrenheitToCelsius.applyAsDouble(32));
+ Assertions.assertEquals(25.0, fahrenheitToCelsius.applyAsDouble(77));
+ Assertions.assertEquals(100.0, fahrenheitToCelsius.applyAsDouble(212));
+ }
+
+ @Test
+ void toDoubleBiFunction() {
+ // 30% discount when it is SALE else 10% standard discount
+ ToDoubleBiFunction discountedPrice =
+ (code, price) -> "SALE".equals(code) ? price * 0.7 : price * 0.9;
+ Assertions.assertEquals(14.0, discountedPrice.applyAsDouble("SALE", 20.0));
+ Assertions.assertEquals(18.0, discountedPrice.applyAsDouble("OFF_SEASON", 20.0));
+ }
+
+ @Test
+ void toIntFunction() {
+ ToIntFunction charCount = input -> input == null ? 0 : input.trim().length();
+
+ Assertions.assertEquals(0, charCount.applyAsInt(null));
+ Assertions.assertEquals(0, charCount.applyAsInt(""));
+ Assertions.assertEquals(3, charCount.applyAsInt("JOY"));
+ }
+
+ @Test
+ void toIntBiFunction() {
+ // discount on product
+ ToIntBiFunction discount =
+ (season, quantity) -> "WINTER".equals(season) || quantity > 100 ? 40 : 10;
+
+ Assertions.assertEquals(40, discount.applyAsInt("WINTER", 50));
+ Assertions.assertEquals(40, discount.applyAsInt("SUMMER", 150));
+ Assertions.assertEquals(10, discount.applyAsInt("FALL", 50));
+ }
+
+ @Test
+ void toLongFunction() {
+ ToLongFunction elapsedTime =
+ input -> input == null ? 0 : input.toInstant().toEpochMilli();
+
+ Assertions.assertEquals(0L, elapsedTime.applyAsLong(null));
+ long now = System.currentTimeMillis();
+ Date nowDate = Date.from(Instant.ofEpochMilli(now));
+ Assertions.assertEquals(now, elapsedTime.applyAsLong(nowDate));
+ }
+
+ @Test
+ void toLongBiFunction() {
+ // discount on product
+ ToLongBiFunction elapsed =
+ (localDateTime, zoneOffset) ->
+ zoneOffset == null
+ ? localDateTime.toEpochSecond(ZoneOffset.UTC)
+ : localDateTime.toEpochSecond(zoneOffset);
+
+ final long now = System.currentTimeMillis();
+ final LocalDateTime nowLocalDateTime = LocalDateTime.ofEpochSecond(now, 0, ZoneOffset.UTC);
+ Assertions.assertEquals(now, elapsed.applyAsLong(nowLocalDateTime, null));
+
+ final long later = now + 1000;
+ final ZoneOffset offset = ZoneOffset.ofHours(5);
+ final LocalDateTime laterLocalDateTime = LocalDateTime.ofEpochSecond(later, 0, offset);
+ Assertions.assertEquals(later, elapsed.applyAsLong(laterLocalDateTime, offset));
+ }
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/OperatorTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/OperatorTest.java
new file mode 100644
index 000000000..8f0b76ec4
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/OperatorTest.java
@@ -0,0 +1,162 @@
+package io.reflectoring.function;
+
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+import java.util.function.*;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.reflectoring.function.custom.ArithmeticOperation;
+import jakarta.validation.constraints.NotNull;
+
+public class OperatorTest {
+ @Test
+ void unaryOperator() {
+ ArithmeticOperation add = (var a, var b) -> a + b;
+ ArithmeticOperation addNullSafe = (@NotNull var a, @NotNull var b) -> a + b;
+ UnaryOperator trim = value -> value == null ? null : value.trim();
+ UnaryOperator upperCase = value -> value == null ? null : value.toUpperCase();
+ Function transform = trim.andThen(upperCase);
+
+ Assertions.assertEquals("joy", trim.apply(" joy "));
+ Assertions.assertEquals(" JOY ", upperCase.apply(" joy "));
+ Assertions.assertEquals("JOY", transform.apply(" joy "));
+ }
+
+ @Test
+ void intUnaryOperator() {
+ // formula y = x^2 + 2x + 1
+ IntUnaryOperator formula = x -> (x * x) + (2 * x) + 1;
+ Assertions.assertEquals(36, formula.applyAsInt(5));
+
+ IntStream input = IntStream.of(2, 3, 4);
+ final int[] result = input.map(formula).toArray();
+ Assertions.assertArrayEquals(new int[] {9, 16, 25}, result);
+
+ // the population doubling every 3 years, one fifth migrate and 10% mortality
+ IntUnaryOperator growth = number -> number * 2;
+ IntUnaryOperator migration = number -> number * 4 / 5;
+ IntUnaryOperator mortality = number -> number * 9 / 10;
+ IntUnaryOperator population = growth.andThen(migration).andThen(mortality);
+ Assertions.assertEquals(1440000, population.applyAsInt(1000000));
+ }
+
+ @Test
+ void longUnaryOperator() {
+ // light travels 186282 miles per seconds
+ LongUnaryOperator distance = time -> time * 186282;
+ // denser medium slows light down
+ LongUnaryOperator slowDown = dist -> dist * 2 / 3;
+ LongUnaryOperator actualDistance = distance.andThen(slowDown);
+
+ Assertions.assertEquals(931410, distance.applyAsLong(5));
+ Assertions.assertEquals(620940, actualDistance.applyAsLong(5));
+
+ final LongStream input = LongStream.of(5, 10, 15);
+ final long[] result = input.map(distance).toArray();
+ Assertions.assertArrayEquals(new long[] {931410L, 1862820L, 2794230L}, result);
+ }
+
+ @Test
+ void doubleUnaryOperator() {
+ DoubleUnaryOperator circleArea = radius -> radius * radius * Math.PI;
+ DoubleUnaryOperator doubleIt = area -> area * 4;
+ DoubleUnaryOperator scaling = circleArea.andThen(doubleIt);
+
+ Assertions.assertEquals(153.93D, circleArea.applyAsDouble(7), 0.01);
+ Assertions.assertEquals(615.75D, scaling.applyAsDouble(7), 0.01);
+
+ final DoubleStream input = DoubleStream.of(7d, 14d, 21d);
+ final double[] result = input.map(circleArea).toArray();
+ Assertions.assertArrayEquals(new double[] {153.93D, 615.75D, 1385.44D}, result, 0.01);
+ }
+
+ @Test
+ void binaryOperator() {
+ LongUnaryOperator factorial =
+ n -> {
+ long result = 1L;
+ for (int i = 1; i <= n; i++) {
+ result *= i;
+ }
+ return result;
+ };
+ // Calculate permutations
+ BinaryOperator npr = (n, r) -> factorial.applyAsLong(n) / factorial.applyAsLong(n - r);
+ // Verify permutations
+ // 3P2 means the number of permutations of 2 that can be achieved from a choice of 3.
+ final Long result3P2 = npr.apply(3L, 2L);
+ Assertions.assertEquals(6L, result3P2);
+
+ // Add two prices
+ BinaryOperator addPrices = Double::sum;
+ // Apply discount
+ UnaryOperator applyDiscount = total -> total * 0.9; // 10% discount
+ // Apply tax
+ UnaryOperator applyTax = total -> total * 1.07; // 7% tax
+ // Composing the final operation
+ BiFunction finalCost =
+ addPrices.andThen(applyDiscount).andThen(applyTax);
+
+ // Prices of two items
+ double item1 = 50.0;
+ double item2 = 100.0;
+ // Calculate final cost
+ double cost = finalCost.apply(item1, item2);
+ // Verify the final calculated cost
+ Assertions.assertEquals(144.45D, cost, 0.01);
+ }
+
+ @Test
+ void intBinaryOperator() {
+ IntBinaryOperator add = Integer::sum;
+ Assertions.assertEquals(10, add.applyAsInt(4, 6));
+
+ IntStream input = IntStream.of(2, 3, 4);
+ OptionalInt result = input.reduce(add);
+ Assertions.assertEquals(OptionalInt.of(9), result);
+ }
+
+ @Test
+ void longBinaryOperator() {
+ // Greatest Common Divisor
+ LongBinaryOperator gcd =
+ (a, b) -> {
+ while (b != 0) {
+ long temp = b;
+ b = a % b;
+ a = temp;
+ }
+ return a;
+ };
+ Assertions.assertEquals(6L, gcd.applyAsLong(54L, 24L));
+
+ LongBinaryOperator add = Long::sum;
+ // Time car traveled
+ LongStream input = LongStream.of(1715785375164L, 1715785385771L);
+ final OptionalLong result = input.reduce(add);
+ Assertions.assertEquals(OptionalLong.of(3431570760935L), result);
+ }
+
+ @Test
+ void doubleBinaryOperator() {
+ DoubleBinaryOperator subtractAreas = (a, b) -> a - b;
+ // Area of a rectangle
+ double rectangleArea = 20.0 * 30.0;
+ // Area of a circle
+ double circleArea = Math.PI * 7.0 * 7.0;
+
+ // Subtract the two areas
+ double difference = subtractAreas.applyAsDouble(rectangleArea, circleArea);
+ Assertions.assertEquals(446.06, difference, 0.01);
+
+ DoubleBinaryOperator add = Double::sum;
+ DoubleStream input = DoubleStream.of(10.2, 5.6, 15.8, 20.12);
+ OptionalDouble result = input.reduce(add);
+ Assertions.assertEquals(OptionalDouble.of(51.72), result);
+ }
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/PredicateTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/PredicateTest.java
new file mode 100644
index 000000000..19f92f42b
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/PredicateTest.java
@@ -0,0 +1,256 @@
+package io.reflectoring.function;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.*;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PredicateTest {
+ // C = Carpenter, W = Welder
+ private final Object[][] workers = {
+ {"C", 24},
+ {"W", 32},
+ {"C", 35},
+ {"W", 40},
+ {"C", 50},
+ {"W", 44},
+ {"C", 30}
+ };
+
+ @Test
+ void testFiltering() {
+ List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+ Predicate isEven = num -> num % 2 == 0;
+
+ List actual = numbers.stream().filter(isEven).toList();
+
+ List expected = List.of(2, 4, 6, 8, 10);
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ void testPredicate() {
+ List numbers = List.of(-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5);
+ Predicate isZero = num -> num == 0;
+ Predicate isPositive = num -> num > 0;
+ Predicate isNegative = num -> num < 0;
+ Predicate isOdd = num -> num % 2 == 1;
+
+ Predicate isPositiveOrZero = isPositive.or(isZero);
+ Predicate isPositiveAndOdd = isPositive.and(isOdd);
+ Predicate isNotPositive = Predicate.not(isPositive);
+ Predicate isNotZero = isZero.negate();
+ Predicate isAlsoZero = isPositive.negate().and(isNegative.negate());
+
+ // check zero or greater
+ Assertions.assertEquals(
+ List.of(0, 1, 2, 3, 4, 5), numbers.stream().filter(isPositiveOrZero).toList());
+
+ // check greater than zero and odd
+ Assertions.assertEquals(List.of(1, 3, 5), numbers.stream().filter(isPositiveAndOdd).toList());
+
+ // check less than zero and negative
+ Assertions.assertEquals(
+ List.of(-5, -4, -3, -2, -1, 0), numbers.stream().filter(isNotPositive).toList());
+
+ // check not zero
+ Assertions.assertEquals(
+ List.of(-5, -4, -3, -2, -1, 1, 2, 3, 4, 5), numbers.stream().filter(isNotZero).toList());
+
+ // check neither positive nor negative
+ Assertions.assertEquals(
+ numbers.stream().filter(isZero).toList(), numbers.stream().filter(isAlsoZero).toList());
+ }
+
+ @Test
+ void testBiPredicate() {
+
+ BiPredicate juniorCarpenterCheck =
+ (worker, age) -> "C".equals(worker) && (age >= 18 && age <= 40);
+
+ BiPredicate groomedCarpenterCheck =
+ (worker, age) -> "C".equals(worker) && (age >= 30 && age <= 40);
+
+ BiPredicate allCarpenterCheck =
+ (worker, age) -> "C".equals(worker) && (age >= 18);
+
+ BiPredicate juniorWelderCheck =
+ (worker, age) -> "W".equals(worker) && (age >= 18 && age <= 40);
+
+ BiPredicate juniorWorkerCheck = juniorCarpenterCheck.or(juniorWelderCheck);
+
+ BiPredicate juniorGroomedCarpenterCheck =
+ juniorCarpenterCheck.and(groomedCarpenterCheck);
+
+ BiPredicate allWelderCheck = allCarpenterCheck.negate();
+
+ final long juniorCarpenterCount =
+ Arrays.stream(workers)
+ .filter(person -> juniorCarpenterCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(3L, juniorCarpenterCount);
+
+ final long juniorWelderCount =
+ Arrays.stream(workers)
+ .filter(person -> juniorWelderCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(2L, juniorWelderCount);
+
+ final long juniorWorkerCount =
+ Arrays.stream(workers)
+ .filter(person -> juniorWorkerCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(5L, juniorWorkerCount);
+
+ final long juniorGroomedCarpenterCount =
+ Arrays.stream(workers)
+ .filter(
+ person -> juniorGroomedCarpenterCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(2L, juniorGroomedCarpenterCount);
+
+ final long allWelderCount =
+ Arrays.stream(workers)
+ .filter(person -> allWelderCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(3L, allWelderCount);
+ }
+
+ @Test
+ void testBiPredicateDefaultMethods() {
+
+ BiPredicate juniorCarpenterCheck =
+ (worker, age) -> "C".equals(worker) && (age >= 18 && age <= 40);
+
+ BiPredicate groomedCarpenterCheck =
+ (worker, age) -> "C".equals(worker) && (age >= 30 && age <= 40);
+
+ BiPredicate allCarpenterCheck =
+ (worker, age) -> "C".equals(worker) && (age >= 18);
+
+ BiPredicate juniorWelderCheck =
+ (worker, age) -> "W".equals(worker) && (age >= 18 && age <= 40);
+
+ BiPredicate juniorWorkerCheck = juniorCarpenterCheck.or(juniorWelderCheck);
+
+ BiPredicate juniorGroomedCarpenterCheck =
+ juniorCarpenterCheck.and(groomedCarpenterCheck);
+
+ BiPredicate allWelderCheck = allCarpenterCheck.negate();
+
+ // test or()
+ final long juniorWorkerCount =
+ Arrays.stream(workers)
+ .filter(person -> juniorWorkerCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(5L, juniorWorkerCount);
+
+ // test and()
+ final long juniorGroomedCarpenterCount =
+ Arrays.stream(workers)
+ .filter(
+ person -> juniorGroomedCarpenterCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(2L, juniorGroomedCarpenterCount);
+
+ // test negate()
+ final long allWelderCount =
+ Arrays.stream(workers)
+ .filter(person -> allWelderCheck.test((String) person[0], (Integer) person[1]))
+ .count();
+ Assertions.assertEquals(3L, allWelderCount);
+ }
+
+ @Test
+ void testIntPredicate() {
+ IntPredicate isZero = num -> num == 0;
+ IntPredicate isPositive = num -> num > 0;
+ IntPredicate isNegative = num -> num < 0;
+ IntPredicate isOdd = num -> num % 2 == 1;
+
+ IntPredicate isPositiveOrZero = isPositive.or(isZero);
+ IntPredicate isPositiveAndOdd = isPositive.and(isOdd);
+ IntPredicate isNotZero = isZero.negate();
+ IntPredicate isAlsoZero = isPositive.negate().and(isNegative.negate());
+
+ // check zero or greater
+ Assertions.assertArrayEquals(
+ new int[] {0, 1, 2, 3, 4, 5}, IntStream.range(-5, 6).filter(isPositiveOrZero).toArray());
+
+ // check greater than zero and odd
+ Assertions.assertArrayEquals(
+ new int[] {1, 3, 5}, IntStream.range(-5, 6).filter(isPositiveAndOdd).toArray());
+
+ // check not zero
+ Assertions.assertArrayEquals(
+ new int[] {-5, -4, -3, -2, -1, 1, 2, 3, 4, 5},
+ IntStream.range(-5, 6).filter(isNotZero).toArray());
+
+ // check neither positive nor negative
+ Assertions.assertArrayEquals(
+ IntStream.range(-5, 6).filter(isZero).toArray(),
+ IntStream.range(-5, 6).filter(isAlsoZero).toArray());
+ }
+
+ @Test
+ void testLongPredicate() {
+ LongPredicate isStopped = num -> num == 0;
+ LongPredicate firstGear = num -> num > 0 && num <= 20;
+ LongPredicate secondGear = num -> num > 20 && num <= 35;
+ LongPredicate thirdGear = num -> num > 35 && num <= 50;
+ LongPredicate forthGear = num -> num > 50 && num <= 80;
+ LongPredicate fifthGear = num -> num > 80;
+ LongPredicate max = num -> num < 150;
+
+ LongPredicate cityDriveCheck = firstGear.or(secondGear).or(thirdGear);
+ LongPredicate drivingCheck = isStopped.negate();
+ LongPredicate highwayCheck = max.and(forthGear.or(fifthGear));
+
+ // check stopped
+ Assertions.assertArrayEquals(
+ new long[] {0L}, LongStream.of(0L, 40L, 60L, 100L).filter(isStopped).toArray());
+
+ // check city speed limit
+ Assertions.assertArrayEquals(
+ new long[] {20L, 50L}, LongStream.of(0L, 20L, 50L, 100L).filter(cityDriveCheck).toArray());
+
+ // check negate
+ Assertions.assertArrayEquals(
+ new long[] {70L, 100L}, LongStream.of(70L, 100L, 200L).filter(highwayCheck).toArray());
+ }
+
+ @Test
+ void testDoublePredicate() {
+ // weight categories (weight in lbs)
+ DoublePredicate underweight = weight -> weight <= 125;
+ DoublePredicate healthy = weight -> weight >= 126 && weight <= 168;
+ DoublePredicate overweight = weight -> weight >= 169 && weight <= 202;
+ DoublePredicate obese = weight -> weight >= 203;
+ DoublePredicate needToLose = weight -> weight >= 169;
+ DoublePredicate notHealthy = healthy.negate();
+ DoublePredicate alsoNotHealthy = underweight.or(overweight).or(obese);
+ DoublePredicate skipSugar = needToLose.and(overweight.or(obese));
+
+ // check need to lose weight
+ Assertions.assertArrayEquals(
+ new double[] {200D}, DoubleStream.of(100D, 140D, 160D, 200D).filter(needToLose).toArray());
+
+ // check need to lose weight
+ Assertions.assertArrayEquals(
+ new double[] {100D, 200D},
+ DoubleStream.of(100D, 140D, 160D, 200D).filter(notHealthy).toArray());
+
+ // check negate()
+ Assertions.assertArrayEquals(
+ DoubleStream.of(100D, 140D, 160D, 200D).filter(notHealthy).toArray(),
+ DoubleStream.of(100D, 140D, 160D, 200D).filter(alsoNotHealthy).toArray());
+
+ // check and()
+ Assertions.assertArrayEquals(
+ new double[] {200D}, DoubleStream.of(100D, 140D, 160D, 200D).filter(skipSugar).toArray());
+ }
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/SupplierTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/SupplierTest.java
new file mode 100644
index 000000000..9f22d5f49
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/SupplierTest.java
@@ -0,0 +1,59 @@
+package io.reflectoring.function;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.*;
+import java.util.stream.DoubleStream;
+import java.util.stream.LongStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class SupplierTest {
+ @Test
+ void supplier() {
+ // Supply random numbers
+ Supplier randomNumberSupplier = () -> new Random().nextInt(100);
+ int result = randomNumberSupplier.get();
+ Assertions.assertTrue(result >= 0 && result < 100);
+ }
+
+ @Test
+ void intSupplier() {
+ IntSupplier nextWinner = () -> new Random().nextInt(100, 200);
+ int result = nextWinner.getAsInt();
+ Assertions.assertTrue(result >= 100 && result < 200);
+ }
+
+ @Test
+ void longSupplier() {
+ LongSupplier nextWinner = () -> new Random().nextLong(100, 200);
+ LongStream winners = LongStream.generate(nextWinner).limit(10);
+ Assertions.assertEquals(10, winners.toArray().length);
+ }
+
+ @Test
+ void doubleSupplier() {
+ // Random data for plotting graph
+ DoubleSupplier weightSupplier = () -> new Random().nextDouble(100, 200);
+ DoubleStream dataSample = DoubleStream.generate(weightSupplier).limit(10);
+ Assertions.assertEquals(10, dataSample.toArray().length);
+ }
+
+ @ParameterizedTest
+ @CsvSource(value = {"ON,true", "OFF,false"})
+ void booleanSupplier(String statusCode, boolean expected) {
+ AtomicReference status = new AtomicReference<>();
+ status.set(statusCode);
+ // Simulate a service health check
+ BooleanSupplier isServiceHealthy =
+ () -> {
+ // Here, we could check the actual health of a service.
+ // simplified for test purpose
+ return status.toString().equals("ON");
+ };
+ boolean result = isServiceHealthy.getAsBoolean();
+ Assertions.assertEquals(expected, result);
+ }
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/custom/ArithmeticOperationTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/custom/ArithmeticOperationTest.java
new file mode 100644
index 000000000..5e1afbeb3
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/custom/ArithmeticOperationTest.java
@@ -0,0 +1,23 @@
+package io.reflectoring.function.custom;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ArithmeticOperationTest {
+
+ @Test
+ void operate() {
+ // Define operations
+ ArithmeticOperation add = (a, b) -> a + b;
+ ArithmeticOperation subtract = (a, b) -> a - b;
+ ArithmeticOperation multiply = (a, b) -> a * b;
+ ArithmeticOperation divide = (a, b) -> a / b;
+
+ // Verify results
+ assertEquals(15, add.operate(10, 5));
+ assertEquals(5, subtract.operate(10, 5));
+ assertEquals(50, multiply.operate(10, 5));
+ assertEquals(2, divide.operate(10, 5));
+ }
+}
diff --git a/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/custom/MethodReferenceTest.java b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/custom/MethodReferenceTest.java
new file mode 100644
index 000000000..96ab8c8ba
--- /dev/null
+++ b/core-java/functional-programming/functional-interfaces/src/test/java/io/reflectoring/function/custom/MethodReferenceTest.java
@@ -0,0 +1,80 @@
+package io.reflectoring.function.custom;
+
+import java.math.BigInteger;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Method reference test. */
+public class MethodReferenceTest {
+ /** Static method reference. */
+ @Test
+ void staticMethodReference() {
+ List numbers = List.of(1, -2, 3, -4, 5);
+ List positiveNumbers = numbers.stream().map(Math::abs).toList();
+ positiveNumbers.forEach(number -> Assertions.assertTrue(number > 0));
+ }
+
+ /** The String number comparator. */
+ static class StringNumberComparator implements Comparator {
+ @Override
+ public int compare(String o1, String o2) {
+ if (o1 == null) {
+ return o2 == null ? 0 : 1;
+ } else if (o2 == null) {
+ return -1;
+ }
+ return o1.compareTo(o2);
+ }
+ }
+
+ /** Instance method reference. */
+ @Test
+ void containingClassInstanceMethodReference() {
+ List numbers = List.of("One", "Two", "Three");
+ List numberChars = numbers.stream().map(String::length).toList();
+ numberChars.forEach(length -> Assertions.assertTrue(length > 0));
+ }
+
+ /** Instance method reference. */
+ @Test
+ void containingObjectInstanceMethodReference() {
+ List numbers = List.of("One", "Two", "Three");
+ StringNumberComparator comparator = new StringNumberComparator();
+ final List sorted = numbers.stream().sorted(comparator::compare).toList();
+ final List expected = List.of("One", "Three", "Two");
+ Assertions.assertEquals(expected, sorted);
+ }
+
+ /** Instance method arbitrary object particular type. */
+ @Test
+ void instanceMethodArbitraryObjectParticularType() {
+ List numbers = List.of(1, 2L, 3.0f, 4.0d);
+ List numberIntValues = numbers.stream().map(Number::intValue).toList();
+ Assertions.assertEquals(List.of(1, 2, 3, 4), numberIntValues);
+ }
+
+ /** Constructor reference. */
+ @Test
+ void constructorReference() {
+ List numbers = List.of("1", "2", "3");
+ Map numberMapping =
+ numbers.stream()
+ .map(BigInteger::new)
+ .collect(Collectors.toMap(BigInteger::toString, Function.identity()));
+ Map expected =
+ new HashMap<>() {
+ {
+ put("1", BigInteger.valueOf(1));
+ put("2", BigInteger.valueOf(2));
+ put("3", BigInteger.valueOf(3));
+ }
+ };
+ Assertions.assertEquals(expected, numberMapping);
+ }
+}
diff --git a/core-java/jackson/jackson/.gitignore b/core-java/jackson/jackson/.gitignore
new file mode 100644
index 000000000..3aff99610
--- /dev/null
+++ b/core-java/jackson/jackson/.gitignore
@@ -0,0 +1,29 @@
+HELP.md
+target/*
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
\ No newline at end of file
diff --git a/core-java/jackson/jackson/pom.xml b/core-java/jackson/jackson/pom.xml
new file mode 100644
index 000000000..39398c4f8
--- /dev/null
+++ b/core-java/jackson/jackson/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+ com.reflectoring
+ jackson
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.13.3
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.13.3
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.0-M1
+ test
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.24
+ provided
+
+
+
+ org.assertj
+ assertj-core
+ 3.23.1
+ test
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Car.java b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Car.java
new file mode 100644
index 000000000..82670d574
--- /dev/null
+++ b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Car.java
@@ -0,0 +1,24 @@
+package com.reflectoring.pojo;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class Car {
+ @JsonSetter("carBrand")
+ private String brand;
+ private Map unrecognizedFields = new HashMap<>();
+
+ @JsonAnySetter
+ public void allSetter(String fieldName, String fieldValue) {
+ unrecognizedFields.put(fieldName, fieldValue);
+ }
+}
diff --git a/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Cat.java b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Cat.java
new file mode 100644
index 000000000..9dc27bf7d
--- /dev/null
+++ b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Cat.java
@@ -0,0 +1,29 @@
+package com.reflectoring.pojo;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+public class Cat {
+ private String name;
+
+ @JsonAnyGetter
+ Map map = Map.of(
+ "name", "Jack",
+ "surname", "wolfskin"
+ );
+
+ @JsonGetter("catName")
+ public String getName() {
+ return name;
+ }
+
+ public Cat(String name) {
+ this.name = name;
+ }
+}
diff --git a/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Dog.java b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Dog.java
new file mode 100644
index 000000000..6b8c0de8e
--- /dev/null
+++ b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Dog.java
@@ -0,0 +1,15 @@
+package com.reflectoring.pojo;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+public class Dog {
+ private String name;
+ @JsonIgnore
+ private Integer age;
+}
diff --git a/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Employee.java b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Employee.java
new file mode 100644
index 000000000..344264dd6
--- /dev/null
+++ b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Employee.java
@@ -0,0 +1,16 @@
+package com.reflectoring.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+public class Employee {
+ private String firstName;
+ private String lastName;
+ private int age;
+}
diff --git a/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Order.java b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Order.java
new file mode 100644
index 000000000..80c8abe24
--- /dev/null
+++ b/core-java/jackson/jackson/src/main/java/com/reflectoring/pojo/Order.java
@@ -0,0 +1,18 @@
+package com.reflectoring.pojo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDate;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class Order {
+ private int id;
+ @JsonFormat(pattern = "dd/MM/yyyy")
+ private LocalDate date;
+}
diff --git a/core-java/jackson/jackson/src/test/java/com/reflectoring/JacksonTest.java b/core-java/jackson/jackson/src/test/java/com/reflectoring/JacksonTest.java
new file mode 100644
index 000000000..2c46bcdf0
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/java/com/reflectoring/JacksonTest.java
@@ -0,0 +1,184 @@
+package com.reflectoring;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.reflectoring.pojo.*;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JacksonTest {
+ ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
+
+ @Test
+ void jsonStringToPojo() throws JsonProcessingException {
+ String employeeJson = "{\n" +
+ " \"firstName\" : \"Jalil\",\n" +
+ " \"lastName\" : \"Jarjanazy\",\n" +
+ " \"age\" : 30\n" +
+ "}";
+
+ Employee employee = objectMapper.readValue(employeeJson, Employee.class);
+
+ assertThat(employee.getFirstName()).isEqualTo("Jalil");
+ }
+
+ @Test
+ void pojoToJsonString() throws JsonProcessingException {
+ Employee employee = new Employee("Mark", "James", 20);
+
+ String json = objectMapper.writeValueAsString(employee);
+
+ assertThat(json).isEqualTo("{\"firstName\":\"Mark\",\"lastName\":\"James\",\"age\":20}");
+ }
+
+ @Test
+ void jsonFileToPojo() throws IOException {
+ File file = new File("src/test/resources/employee.json");
+
+ Employee employee = objectMapper.readValue(file, Employee.class);
+
+ assertThat(employee.getAge()).isEqualTo(44);
+ assertThat(employee.getLastName()).isEqualTo("Simpson");
+ assertThat(employee.getFirstName()).isEqualTo("Homer");
+ }
+
+ @Test
+ void byteArrayToPojo() throws IOException {
+ String employeeJson = "{\n" +
+ " \"firstName\" : \"Jalil\",\n" +
+ " \"lastName\" : \"Jarjanazy\",\n" +
+ " \"age\" : 30\n" +
+ "}";
+
+ Employee employee = objectMapper.readValue(employeeJson.getBytes(), Employee.class);
+
+ assertThat(employee.getFirstName()).isEqualTo("Jalil");
+ }
+
+ @Test
+ void fileToListOfPojos() throws IOException {
+ File file = new File("src/test/resources/employeeList.json");
+ List employeeList = objectMapper.readValue(file, new TypeReference<>(){});
+
+ assertThat(employeeList).hasSize(2);
+ assertThat(employeeList.get(0).getAge()).isEqualTo(33);
+ assertThat(employeeList.get(0).getLastName()).isEqualTo("Simpson");
+ assertThat(employeeList.get(0).getFirstName()).isEqualTo("Marge");
+ }
+
+ @Test
+ void fileToMap() throws IOException {
+ File file = new File("src/test/resources/employee.json");
+ Map employee = objectMapper.readValue(file, new TypeReference<>(){});
+
+ assertThat(employee.keySet()).containsExactly("firstName", "lastName", "age");
+
+ assertThat(employee.get("firstName")).isEqualTo("Homer");
+ assertThat(employee.get("lastName")).isEqualTo("Simpson");
+ assertThat(employee.get("age")).isEqualTo(44);
+ }
+
+ @Test
+ void fileToPojoWithUnknownProperties() throws IOException {
+ File file = new File("src/test/resources/employeeWithUnknownProperties.json");
+
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ Employee employee = objectMapper.readValue(file, Employee.class);
+
+ assertThat(employee.getFirstName()).isEqualTo("Homer");
+ assertThat(employee.getLastName()).isEqualTo("Simpson");
+ assertThat(employee.getAge()).isEqualTo(44);
+ }
+
+ @Test
+ void orderToJson() throws JsonProcessingException {
+ Order order = new Order(1, LocalDate.of(1900,2,1));
+
+ String json = objectMapper.writeValueAsString(order);
+
+ System.out.println(json);
+ }
+
+ @Test
+ void orderToJsonWithDate() throws JsonProcessingException {
+ Order order = new Order(1, LocalDate.of(2023, 1, 1));
+
+ String json = objectMapper.writeValueAsString(order);
+
+ System.out.println(json);
+ }
+ @Test
+ void fileToOrder() throws IOException {
+ File file = new File("src/test/resources/order.json");
+
+ Order order = objectMapper.readValue(file, Order.class);
+
+ assertThat(order.getDate().getYear()).isEqualTo(2000);
+ assertThat(order.getDate().getMonthValue()).isEqualTo(4);
+ assertThat(order.getDate().getDayOfMonth()).isEqualTo(30);
+ }
+ @Test
+ void fileToCar() throws IOException {
+ File file = new File("src/test/resources/car.json");
+
+ Car car = objectMapper.readValue(file, Car.class);
+
+ assertThat(car.getBrand()).isEqualTo("BMW");
+ }
+
+ @Test
+ void fileToUnrecognizedCar() throws IOException {
+ File file = new File("src/test/resources/carUnrecognized.json");
+
+ Car car = objectMapper.readValue(file, Car.class);
+
+ assertThat(car.getUnrecognizedFields()).containsKey("productionYear");
+ }
+
+ @Test
+ void catToJson() throws JsonProcessingException {
+ Cat cat = new Cat("Monica");
+
+ String json = objectMapper.writeValueAsString(cat);
+
+ System.out.println(json);
+
+ }
+
+ @Test
+ void catToJsonWithMap() throws JsonProcessingException {
+ Cat cat = new Cat("Monica");
+
+ String json = objectMapper.writeValueAsString(cat);
+
+ System.out.println(json);
+ }
+
+ @Test
+ void dogToJson() throws JsonProcessingException {
+ Dog dog = new Dog("Max", 3);
+
+ String json = objectMapper.writeValueAsString(dog);
+
+ System.out.println(json);
+ }
+ @Test
+ void fileToDog() throws IOException {
+ File file = new File("src/test/resources/dog.json");
+
+ Dog dog = objectMapper.readValue(file, Dog.class);
+
+ assertThat(dog.getName()).isEqualTo("bobby");
+ assertThat(dog.getAge()).isNull();
+ }
+}
diff --git a/core-java/jackson/jackson/src/test/resources/car.json b/core-java/jackson/jackson/src/test/resources/car.json
new file mode 100644
index 000000000..4f231d645
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/car.json
@@ -0,0 +1,3 @@
+{
+ "carBrand" : "BMW"
+}
\ No newline at end of file
diff --git a/core-java/jackson/jackson/src/test/resources/carUnrecognized.json b/core-java/jackson/jackson/src/test/resources/carUnrecognized.json
new file mode 100644
index 000000000..70a0dc0bd
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/carUnrecognized.json
@@ -0,0 +1,4 @@
+{
+ "carBrand" : "BMW",
+ "productionYear": 1996
+}
\ No newline at end of file
diff --git a/core-java/jackson/jackson/src/test/resources/dog.json b/core-java/jackson/jackson/src/test/resources/dog.json
new file mode 100644
index 000000000..96ca22d1e
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/dog.json
@@ -0,0 +1,4 @@
+{
+ "name" : "bobby",
+ "age" : 5
+}
\ No newline at end of file
diff --git a/core-java/jackson/jackson/src/test/resources/employee.json b/core-java/jackson/jackson/src/test/resources/employee.json
new file mode 100644
index 000000000..15cda002a
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/employee.json
@@ -0,0 +1,5 @@
+{
+ "firstName":"Homer",
+ "lastName":"Simpson",
+ "age":44
+}
diff --git a/core-java/jackson/jackson/src/test/resources/employeeList.json b/core-java/jackson/jackson/src/test/resources/employeeList.json
new file mode 100644
index 000000000..5a05a3bf3
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/employeeList.json
@@ -0,0 +1,12 @@
+[
+ {
+ "firstName":"Marge",
+ "lastName":"Simpson",
+ "age":33
+ },
+ {
+ "firstName":"Homer",
+ "lastName":"Simpson",
+ "age":44
+ }
+]
diff --git a/core-java/jackson/jackson/src/test/resources/employeeWithUnknownProperties.json b/core-java/jackson/jackson/src/test/resources/employeeWithUnknownProperties.json
new file mode 100644
index 000000000..8778b1136
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/employeeWithUnknownProperties.json
@@ -0,0 +1,6 @@
+{
+ "firstName":"Homer",
+ "lastName":"Simpson",
+ "age":44,
+ "department": "IT"
+}
diff --git a/core-java/jackson/jackson/src/test/resources/order.json b/core-java/jackson/jackson/src/test/resources/order.json
new file mode 100644
index 000000000..8d94ba981
--- /dev/null
+++ b/core-java/jackson/jackson/src/test/resources/order.json
@@ -0,0 +1,4 @@
+{
+ "id" : 1,
+ "date" : "30/04/2000"
+}
\ No newline at end of file
diff --git a/core-java/junit5-parameterized-tests/.gitignore b/core-java/junit5-parameterized-tests/.gitignore
new file mode 100644
index 000000000..2266015c4
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/.gitignore
@@ -0,0 +1,8 @@
+# Maven
+target/
+.mvn/
+
+# eclipse project file
+.settings/
+.classpath
+.project
\ No newline at end of file
diff --git a/core-java/junit5-parameterized-tests/README.md b/core-java/junit5-parameterized-tests/README.md
new file mode 100644
index 000000000..6f79c72db
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/README.md
@@ -0,0 +1,3 @@
+# Related Blog Posts
+
+* [Parameterized tests with JUnit 5](https://reflectoring.io/junit5-parameterized-tests/)
\ No newline at end of file
diff --git a/core-java/junit5-parameterized-tests/mvnw b/core-java/junit5-parameterized-tests/mvnw
new file mode 100755
index 000000000..b7f064624
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/mvnw
@@ -0,0 +1,287 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.1.1
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir"; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname $0)")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $wrapperUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ QUIET="--quiet"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ QUIET=""
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath"
+ fi
+ [ $? -eq 0 ] || rm -f "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ QUIET="--silent"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ QUIET=""
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L
+ fi
+ [ $? -eq 0 ] || rm -f "$wrapperJarPath"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=`cygpath --path --windows "$javaSource"`
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/core-java/junit5-parameterized-tests/mvnw.cmd b/core-java/junit5-parameterized-tests/mvnw.cmd
new file mode 100644
index 000000000..cba1f040d
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/mvnw.cmd
@@ -0,0 +1,187 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.1.1
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/core-java/junit5-parameterized-tests/pom.xml b/core-java/junit5-parameterized-tests/pom.xml
new file mode 100644
index 000000000..a269c8703
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/pom.xml
@@ -0,0 +1,33 @@
+
+ 4.0.0
+ io.refactoring
+ junit5-parameterized-tests
+ 1.0.0-SNAPSHOT
+
+ 11
+ 11
+
+ 5.9.2
+ 3.12.0
+ 3.24.1
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-jupiter-params.version}
+ test
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/ArgumentConversionTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/ArgumentConversionTest.java
new file mode 100644
index 000000000..49ce885ff
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/ArgumentConversionTest.java
@@ -0,0 +1,43 @@
+package source.argument.conversion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.time.temporal.ChronoUnit;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.converter.ConvertWith;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class ArgumentConversionTest {
+
+ @ParameterizedTest
+ @ValueSource(ints = { 2, 4 })
+ void checkWideningArgumentConversion(long number) {
+ assertEquals(0, number % 2);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @ValueSource(strings = "DAYS")
+ void checkImplicitArgumentConversion(ChronoUnit argument) {
+ assertNotNull(argument.name());
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @ValueSource(strings = { "Name1", "Name2" })
+ void checkImplicitFallbackArgumentConversion(Person person) {
+ assertNotNull(person.getName());
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @ValueSource(ints = { 100 })
+ void checkExplicitArgumentConversion(@ConvertWith(StringSimpleArgumentConverter.class) String argument) {
+ assertEquals("100", argument);
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/Person.java b/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/Person.java
new file mode 100644
index 000000000..7c1cdb6b0
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/Person.java
@@ -0,0 +1,18 @@
+package source.argument.conversion;
+
+public class Person {
+
+ private String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/StringSimpleArgumentConverter.java b/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/StringSimpleArgumentConverter.java
new file mode 100644
index 000000000..fcae7321b
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/argument/conversion/StringSimpleArgumentConverter.java
@@ -0,0 +1,13 @@
+package source.argument.conversion;
+
+import org.junit.jupiter.params.converter.ArgumentConversionException;
+import org.junit.jupiter.params.converter.SimpleArgumentConverter;
+
+public class StringSimpleArgumentConverter extends SimpleArgumentConverter {
+
+ @Override
+ protected Object convert(Object source, Class> targetType) throws ArgumentConversionException {
+ return String.valueOf(source);
+ }
+
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/ArgumentsSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/ArgumentsSourceTest.java
new file mode 100644
index 000000000..264f11622
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/ArgumentsSourceTest.java
@@ -0,0 +1,39 @@
+package source.arguments;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+
+public class ArgumentsSourceTest {
+
+ @ParameterizedTest
+ @ArgumentsSource(ExternalArgumentsProvider.class)
+ void checkExternalArgumentsSource(int number, String expected) {
+ assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2,
+ "Supplied number " + number + " is not an " + expected + " number");
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @ArgumentsSource(NestedArgumentsProvider.class)
+ void checkNestedArgumentsSource(int number, String expected) {
+ assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2,
+ "Supplied number " + number + " is not an " + expected + " number");
+ }
+
+ static class NestedArgumentsProvider implements ArgumentsProvider {
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) throws Exception {
+ return Stream.of(Arguments.of(2, "even"), Arguments.of(3, "odd"));
+ }
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/ExternalArgumentsProvider.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/ExternalArgumentsProvider.java
new file mode 100644
index 000000000..2295fb065
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/ExternalArgumentsProvider.java
@@ -0,0 +1,16 @@
+package source.arguments;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+
+public class ExternalArgumentsProvider implements ArgumentsProvider {
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) throws Exception {
+ return Stream.of(Arguments.of(2, "even"),
+ Arguments.of(3, "odd"));
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/ArgumentsAccessorTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/ArgumentsAccessorTest.java
new file mode 100644
index 000000000..99692a81e
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/ArgumentsAccessorTest.java
@@ -0,0 +1,18 @@
+package source.arguments.aggregator;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class ArgumentsAccessorTest {
+
+ @ParameterizedTest
+ @CsvSource({ "John, 20",
+ "Harry, 30" })
+ void checkArgumentsAccessor(ArgumentsAccessor arguments) {
+ Person person = new Person(arguments.getString(0), arguments.getInteger(1));
+ assertTrue(person.getAge() > 19, person.getName() + " is a teenager");
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/ArgumentsAggregatorTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/ArgumentsAggregatorTest.java
new file mode 100644
index 000000000..19b0aa316
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/ArgumentsAggregatorTest.java
@@ -0,0 +1,24 @@
+package source.arguments.aggregator;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.aggregator.AggregateWith;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class ArgumentsAggregatorTest {
+
+ @ParameterizedTest
+ @CsvSource({ "John, 20", "Harry, 30" })
+ void checkArgumentsAggregator(@AggregateWith(PersonArgumentsAggregator.class) Person person) {
+ assertTrue(person.getAge() > 19, person.getName() + " is a teenager");
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @CsvSource({ "John, 20", "Harry, 30" })
+ void checkCustomAggregatorAnnotation(@CsvToPerson Person person) {
+ assertTrue(person.getAge() > 19, person.getName() + " is a teenager");
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/CsvToPerson.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/CsvToPerson.java
new file mode 100644
index 000000000..5186deb11
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/CsvToPerson.java
@@ -0,0 +1,15 @@
+package source.arguments.aggregator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.params.aggregator.AggregateWith;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@AggregateWith(PersonArgumentsAggregator.class)
+public @interface CsvToPerson {
+
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/Person.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/Person.java
new file mode 100644
index 000000000..10c2b7144
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/Person.java
@@ -0,0 +1,30 @@
+package source.arguments.aggregator;
+
+public class Person {
+
+ private String name;
+
+ private int age;
+
+ public Person(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/PersonArgumentsAggregator.java b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/PersonArgumentsAggregator.java
new file mode 100644
index 000000000..743bfd825
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/arguments/aggregator/PersonArgumentsAggregator.java
@@ -0,0 +1,16 @@
+package source.arguments.aggregator;
+
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
+import org.junit.jupiter.params.aggregator.ArgumentsAggregationException;
+import org.junit.jupiter.params.aggregator.ArgumentsAggregator;
+
+public class PersonArgumentsAggregator implements ArgumentsAggregator {
+
+ @Override
+ public Object aggregateArguments(ArgumentsAccessor arguments, ParameterContext context)
+ throws ArgumentsAggregationException {
+ return new Person(arguments.getString(0), arguments.getInteger(1));
+ }
+
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/assertj/AssertJTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/assertj/AssertJTest.java
new file mode 100644
index 000000000..c9abf8101
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/assertj/AssertJTest.java
@@ -0,0 +1,27 @@
+package source.assertj;
+
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class AssertJTest {
+
+ @ParameterizedTest
+ @MethodSource("checkNumberArgs")
+ void checkNumber(int number, Consumer consumer) {
+
+ consumer.accept(number);
+ }
+
+ static Stream checkNumberArgs() {
+
+ Consumer evenConsumer = i -> Assertions.assertThat(i % 2).isZero();
+ Consumer oddConsumer = i -> Assertions.assertThat(i % 2).isEqualTo(1);
+
+ return Stream.of(Arguments.of(2, evenConsumer), Arguments.of(3, oddConsumer));
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/csv/CsvSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/csv/CsvSourceTest.java
new file mode 100644
index 000000000..310907f68
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/csv/CsvSourceTest.java
@@ -0,0 +1,17 @@
+package source.csv;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class CsvSourceTest {
+
+ @ParameterizedTest
+ @CsvSource({ "2, even",
+ "3, odd"})
+ void checkCsvSource(int number, String expected) {
+ assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2);
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/csv/file/CsvFileSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/csv/file/CsvFileSourceTest.java
new file mode 100644
index 000000000..3c8544146
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/csv/file/CsvFileSourceTest.java
@@ -0,0 +1,27 @@
+package source.csv.file;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvFileSource;
+
+public class CsvFileSourceTest {
+
+ @ParameterizedTest
+ @CsvFileSource(files = "src/test/resources/csv-file-source.csv", numLinesToSkip = 1)
+ void checkCsvFileSource(int number, String expected) {
+ assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @CsvFileSource(files = "src/test/resources/csv-file-source_attributes.csv",
+ delimiterString = "|",
+ lineSeparator = "||",
+ numLinesToSkip = 1)
+ void checkCsvFileSourceAttributes(int number, String expected) {
+ assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2);
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/enumeration/EnumSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/enumeration/EnumSourceTest.java
new file mode 100644
index 000000000..411d4dfb5
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/enumeration/EnumSourceTest.java
@@ -0,0 +1,25 @@
+package source.enumeration;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.time.temporal.ChronoUnit;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+public class EnumSourceTest {
+
+ @ParameterizedTest
+ @EnumSource(ChronoUnit.class)
+ void checkEnumSourceValue(ChronoUnit unit) {
+ assertNotNull(unit);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @EnumSource(names = { "DAYS", "HOURS" })
+ void checkEnumSourceNames(ChronoUnit unit) {
+ assertNotNull(unit);
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/method/ExternalMethodSource.java b/core-java/junit5-parameterized-tests/src/test/java/source/method/ExternalMethodSource.java
new file mode 100644
index 000000000..e75378e5f
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/method/ExternalMethodSource.java
@@ -0,0 +1,11 @@
+package source.method;
+
+import java.util.stream.Stream;
+
+public class ExternalMethodSource {
+
+ static Stream checkExternalMethodSourceArgs() {
+ return Stream.of("a1", "b2");
+ }
+
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/method/ExternalMethodSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/method/ExternalMethodSourceTest.java
new file mode 100644
index 000000000..aabcb7bf8
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/method/ExternalMethodSourceTest.java
@@ -0,0 +1,17 @@
+package source.method;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class ExternalMethodSourceTest {
+
+ // Note: The test will try to load the external method
+ @ParameterizedTest
+ @MethodSource("source.method.ExternalMethodSource#checkExternalMethodSourceArgs")
+ void checkExternalMethodSource(String word) {
+ assertTrue(StringUtils.isAlphanumeric(word), "Supplied word is not alpha-numeric");
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/method/MethodSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/method/MethodSourceTest.java
new file mode 100644
index 000000000..cd6b6cd52
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/method/MethodSourceTest.java
@@ -0,0 +1,52 @@
+package source.method;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MethodSourceTest {
+
+ // Note: The test will try to load the supplied method
+ @ParameterizedTest
+ @MethodSource("checkExplicitMethodSourceArgs")
+ void checkExplicitMethodSource(String word) {
+ assertTrue(StringUtils.isAlphanumeric(word), "Supplied word is not alpha-numeric");
+ }
+
+ static Stream checkExplicitMethodSourceArgs() {
+ return Stream.of("a1", "b2");
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // Note: The test will search for the source method that matches the test-case
+ // method name
+ @ParameterizedTest
+ @MethodSource
+ void checkImplicitMethodSource(String word) {
+ assertTrue(StringUtils.isAlphanumeric(word), "Supplied word is not alpha-numeric");
+ }
+
+ static Stream checkImplicitMethodSource() {
+ return Stream.of("a1", "b2");
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // Note: The test will automatically map arguments based on the index
+ @ParameterizedTest
+ @MethodSource
+ void checkMultiArgumentsMethodSource(int number, String expected) {
+ assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2);
+ }
+
+ static Stream checkMultiArgumentsMethodSource() {
+ return Stream.of(Arguments.of(2, "even"), Arguments.of(3, "odd"));
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/null_empty/NullEmptySourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/null_empty/NullEmptySourceTest.java
new file mode 100644
index 000000000..02d61966a
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/null_empty/NullEmptySourceTest.java
@@ -0,0 +1,44 @@
+package source.null_empty;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EmptySource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class NullEmptySourceTest {
+
+ @ParameterizedTest
+ @NullSource
+ void checkNull(String value) {
+ assertEquals(null, value);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @EmptySource
+ void checkEmpty(String value) {
+ assertEquals("", value);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ void checkNullAndEmpty(String value) {
+ assertTrue(value == null || value.isEmpty());
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = { " ", " " })
+ void checkNullEmptyAndBlank(String value) {
+ assertTrue(value == null || value.isBlank());
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/java/source/value/ValueSourceTest.java b/core-java/junit5-parameterized-tests/src/test/java/source/value/ValueSourceTest.java
new file mode 100644
index 000000000..d27df981d
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/java/source/value/ValueSourceTest.java
@@ -0,0 +1,25 @@
+package source.value;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class ValueSourceTest {
+
+ @ParameterizedTest
+ @ValueSource(ints = { 2, 4 })
+ void checkEvenNumber(int number) {
+ assertEquals(0, number % 2, "Supplied number is not an even number");
+ }
+
+ // ---------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @ValueSource(strings = { "a1", "b2" })
+ void checkAlphanumeric(String word) {
+ assertTrue(StringUtils.isAlphanumeric(word), "Supplied word is not alpha-numeric");
+ }
+}
diff --git a/core-java/junit5-parameterized-tests/src/test/resources/csv-file-source.csv b/core-java/junit5-parameterized-tests/src/test/resources/csv-file-source.csv
new file mode 100644
index 000000000..f1f62675c
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/resources/csv-file-source.csv
@@ -0,0 +1,3 @@
+NUMBER, ODD_EVEN
+2, even
+3, odd
\ No newline at end of file
diff --git a/core-java/junit5-parameterized-tests/src/test/resources/csv-file-source_attributes.csv b/core-java/junit5-parameterized-tests/src/test/resources/csv-file-source_attributes.csv
new file mode 100644
index 000000000..df408d308
--- /dev/null
+++ b/core-java/junit5-parameterized-tests/src/test/resources/csv-file-source_attributes.csv
@@ -0,0 +1,3 @@
+|| NUMBER | ODD_EVEN ||
+|| 2 | even ||
+|| 3 | odd ||
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/.gitignore b/core-java/lombok/be-informed-with-lombok/.gitignore
new file mode 100644
index 000000000..3aff99610
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/.gitignore
@@ -0,0 +1,29 @@
+HELP.md
+target/*
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/maven-wrapper.jar b/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/maven-wrapper.properties b/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..00ce563ad
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/core-java/lombok/be-informed-with-lombok/README.md b/core-java/lombok/be-informed-with-lombok/README.md
new file mode 100644
index 000000000..502dcf908
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/README.md
@@ -0,0 +1,6 @@
+# Article Summary
+This article briefly explains some good and bad usages of Lombok.
+
+# Related Blog Posts for more information
+* [How annotation processing works in java](https://reflectoring.io/java-annotation-processing/)
+* [What is an immutable object](https://reflectoring.io/java-immutables/)
diff --git a/core-java/lombok/be-informed-with-lombok/checkstyle.xml b/core-java/lombok/be-informed-with-lombok/checkstyle.xml
new file mode 100644
index 000000000..d157d441b
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/checkstyle.xml
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/mvnw b/core-java/lombok/be-informed-with-lombok/mvnw
new file mode 100644
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/core-java/lombok/be-informed-with-lombok/mvnw.cmd b/core-java/lombok/be-informed-with-lombok/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/core-java/lombok/be-informed-with-lombok/pom.xml b/core-java/lombok/be-informed-with-lombok/pom.xml
new file mode 100644
index 000000000..c73112bbd
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/pom.xml
@@ -0,0 +1,139 @@
+
+ 4.0.0
+ com.reflectoring
+ lombok-example
+ 0.0.1-SNAPSHOT
+ lombok-example
+
+ 11
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.6.6
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.hsqldb
+ hsqldb
+ 2.4.0
+ runtime
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.7.0
+ test
+
+
+ org.junit.platform
+ junit-platform-commons
+ 1.7.0
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.7.0
+ test
+
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.1.1
+
+ checkstyle.xml
+ UTF-8
+ UTF-8
+ true
+ true
+ false
+
+
+
+ verify
+ verify
+
+ check
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.5.3.0
+
+
+ com.github.spotbugs
+ spotbugs
+ 4.6.0
+
+
+
+ UTF-8
+ UTF-8
+ false
+ spotbugs-exclude.xml
+
+
+
+ verify
+ verify
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/spotbugs-exclude.xml b/core-java/lombok/be-informed-with-lombok/spotbugs-exclude.xml
new file mode 100644
index 000000000..97f9e896a
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/spotbugs-exclude.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/delombok/Book.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/delombok/Book.java
new file mode 100644
index 000000000..92f207ed3
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/delombok/Book.java
@@ -0,0 +1,69 @@
+package com.reflectoring.delombok;
+
+import com.reflectoring.lombok.model.Author;
+import com.reflectoring.lombok.model.Genre;
+
+import java.util.List;
+
+public class Book {
+ private String isbn;
+
+ private String publication;
+
+ private String title;
+
+ private List authors;
+
+ private final Genre genre = Genre.FICTION;
+
+ public Book(String isbn, String publication, String title, List authors) {
+ this.isbn = isbn;
+ this.publication = publication;
+ this.title = title;
+ this.authors = authors;
+ }
+
+ public String getIsbn() {
+ return this.isbn;
+ }
+
+ public String getPublication() {
+ return this.publication;
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public List getAuthors() {
+ return this.authors;
+ }
+
+ public Genre getGenre() {
+ return this.genre;
+ }
+
+ public void setIsbn(String isbn) {
+ this.isbn = isbn;
+ }
+
+ public void setPublication(String publication) {
+ this.publication = publication;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public void setAuthors(List authors) {
+ this.authors = authors;
+ }
+
+ public String toString() {
+ return "Book(isbn=" + this.getIsbn() + "," +
+ " publication=" + this.getPublication() +
+ ", title=" + this.getTitle() + ", " +
+ "authors=" + this.getAuthors() + ", " +
+ "genre=" + this.getGenre() + ")";
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/Application.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/Application.java
new file mode 100644
index 000000000..ce9c7c4e7
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/Application.java
@@ -0,0 +1,53 @@
+package com.reflectoring.lombok;
+
+import com.reflectoring.lombok.model.persistence.Book;
+import com.reflectoring.lombok.model.persistence.Publisher;
+import com.reflectoring.lombok.repository.BookRepository;
+import com.reflectoring.lombok.repository.PublisherRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import java.util.Set;
+
+@SpringBootApplication
+public class Application implements CommandLineRunner {
+
+ private static Logger log = LoggerFactory.getLogger(Application.class);
+
+ @Autowired
+ private BookRepository bookRepository;
+
+ @Autowired
+ private PublisherRepository publisherRepository;
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+
+ // Add Data to tables - START
+ Publisher publisher1 = Publisher.builder().id(5000).name("Birdie Publications").build();
+ Publisher publisher2 = Publisher.builder().id(5001).name("KLM Publications").build();
+ Publisher publisher3 = Publisher.builder().id(5002).name("Wallace Books").build();
+
+ publisherRepository.save(publisher1);
+ publisherRepository.save(publisher2);
+ publisherRepository.save(publisher3);
+
+ Set pubList1 = Set.of(publisher1, publisher2);
+ Book book1 = Book.builder().id(1000).name("BookA").publishers(pubList1).build();
+
+ Set pubList2 = Set.of(publisher2, publisher3);
+ Book book2 = Book.builder().id(1001).name("BookB").publishers(pubList2).build();
+
+ bookRepository.save(book1);
+ bookRepository.save(book2);
+ // Add Data to tables - END
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/configuration/ServiceConfiguration.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/configuration/ServiceConfiguration.java
new file mode 100644
index 000000000..841dd64d7
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/configuration/ServiceConfiguration.java
@@ -0,0 +1,21 @@
+package com.reflectoring.lombok.configuration;
+
+import com.reflectoring.lombok.processor.DataProcessor;
+import com.reflectoring.lombok.processor.FileDataProcessor;
+import com.reflectoring.lombok.service.SneakyThrowsService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ServiceConfiguration {
+
+ @Bean
+ public SneakyThrowsService sneakyThrowsService(DataProcessor dataProcessor) {
+ return new SneakyThrowsService(dataProcessor);
+ }
+
+ @Bean
+ public DataProcessor dataProcessor() {
+ return new FileDataProcessor();
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/controller/SneakyThrowsController.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/controller/SneakyThrowsController.java
new file mode 100644
index 000000000..c403b26f5
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/controller/SneakyThrowsController.java
@@ -0,0 +1,44 @@
+package com.reflectoring.lombok.controller;
+
+import com.reflectoring.lombok.service.SneakyThrowsService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@Slf4j
+public class SneakyThrowsController {
+
+ private final SneakyThrowsService service;
+
+ public SneakyThrowsController(SneakyThrowsService service) {
+ this.service = service;
+ }
+
+
+ @RequestMapping(value = "/api/sneakyThrows", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Map sneakyThrows() {
+ service.getFileData();
+ return Collections.singletonMap("status", "SUCCESS");
+ }
+
+ /*
+ Might be a bad design to add @SneakyThrows to multiple bubbling checked exceptions.
+ */
+ @ExceptionHandler(IOException.class)
+ public Map handleException(IOException e) {
+ e.printStackTrace();
+ return Collections.singletonMap("status", "FAIL");
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Account.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Account.java
new file mode 100644
index 000000000..a9c4de6ef
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Account.java
@@ -0,0 +1,14 @@
+package com.reflectoring.lombok.model;
+
+import lombok.Builder;
+
+@Builder
+public class Account {
+ private String acctNo;
+
+ private String acctName;
+
+ private String dateOfJoin;
+
+ private String acctStatus;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Author.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Author.java
new file mode 100644
index 000000000..83552d778
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Author.java
@@ -0,0 +1,17 @@
+package com.reflectoring.lombok.model;
+
+import java.util.Date;
+
+public class Author {
+ private String id;
+
+ private String name;
+
+ private int noOfBooksPublished;
+
+ private Date dob;
+
+ private String email;
+
+ private String countryOfResidence;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Book.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Book.java
new file mode 100644
index 000000000..c71feb70a
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Book.java
@@ -0,0 +1,24 @@
+package com.reflectoring.lombok.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.util.List;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@ToString
+public class Book {
+ private String isbn;
+
+ private String publication;
+
+ private String title;
+
+ private List authors;
+
+ private final Genre genre = Genre.FICTION;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Customer.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Customer.java
new file mode 100644
index 000000000..be570afdb
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Customer.java
@@ -0,0 +1,13 @@
+package com.reflectoring.lombok.model;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class Customer {
+ private String id;
+ private String name;
+ private Gender gender;
+ private String dateOfBirth;
+ private String age;
+ private String socialSecurityNo;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/CustomerDetails.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/CustomerDetails.java
new file mode 100644
index 000000000..16a3aef36
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/CustomerDetails.java
@@ -0,0 +1,53 @@
+package com.reflectoring.lombok.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+@AllArgsConstructor
+public class CustomerDetails {
+
+ private String id;
+ private String name;
+ private Address address;
+ private Gender gender;
+ private String dateOfBirth;
+ private String age;
+ private String socialSecurityNo;
+ private Contact contactDetails;
+ private DriverLicense driverLicense;
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ public static class Address {
+ private String id;
+ private String buildingNm;
+ private String blockNo;
+ private String streetNm;
+ private String city;
+ private int postcode;
+ private String state;
+ private String country;
+ }
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ public static class DriverLicense {
+ private String drivingLicenseNo;
+ private String licenseIssueState;
+ }
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ public static class Contact {
+ private String id;
+ private String email;
+ private String phoneNo;
+
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Gender.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Gender.java
new file mode 100644
index 000000000..0f737b58b
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Gender.java
@@ -0,0 +1,7 @@
+package com.reflectoring.lombok.model;
+
+public enum Gender {
+ MALE,
+ FEMALE,
+ UNKNOWN
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Genre.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Genre.java
new file mode 100644
index 000000000..f4cb9dfb2
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Genre.java
@@ -0,0 +1,8 @@
+package com.reflectoring.lombok.model;
+
+public enum Genre {
+ FICTION,
+ NONFICTION,
+ POETRY,
+ DRAMA
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Job.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Job.java
new file mode 100644
index 000000000..cb530a910
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Job.java
@@ -0,0 +1,12 @@
+package com.reflectoring.lombok.model;
+
+import lombok.Builder;
+import lombok.NonNull;
+
+@Builder
+public class Job {
+ private String id;
+
+ @NonNull
+ private JobType jobType;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/JobType.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/JobType.java
new file mode 100644
index 000000000..1caaf4a3b
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/JobType.java
@@ -0,0 +1,7 @@
+package com.reflectoring.lombok.model;
+
+public enum JobType {
+ PLUMBER,
+ BUILDER,
+ CARPENTER
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Person.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Person.java
new file mode 100644
index 000000000..efef3f101
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/Person.java
@@ -0,0 +1,13 @@
+package com.reflectoring.lombok.model;
+
+import lombok.Value;
+
+import java.util.List;
+
+@Value
+public class Person {
+ private String firstName;
+ private String lastName;
+ private String socialSecurityNo;
+ private List hobbies;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/persistence/Book.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/persistence/Book.java
new file mode 100644
index 000000000..0b5bf5a82
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/persistence/Book.java
@@ -0,0 +1,37 @@
+package com.reflectoring.lombok.model.persistence;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+@Entity
+@Table(name = "BOOK")
+@EqualsAndHashCode // Avoid this annotation with JPA
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+//@ToString //Avoid this annotation with JPA
+public class Book implements Serializable {
+ @Id
+ private long id;
+
+ private String name;
+
+ @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
+ @JoinTable(name = "publisher_book",
+ joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
+ inverseJoinColumns = @JoinColumn(name = "publisher_id", referencedColumnName = "id"))
+ private Set publishers;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/persistence/Publisher.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/persistence/Publisher.java
new file mode 100644
index 000000000..6593172fd
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/model/persistence/Publisher.java
@@ -0,0 +1,37 @@
+package com.reflectoring.lombok.model.persistence;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+@Entity
+@Getter
+@Setter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+//@ToString //Avoid this annotation with JPA
+public class Publisher implements Serializable {
+
+ @Id
+ private long id;
+
+ private String name;
+
+ @ManyToMany(mappedBy = "publishers")
+ private Set books;
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/processor/DataProcessor.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/processor/DataProcessor.java
new file mode 100644
index 000000000..fc1d55275
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/processor/DataProcessor.java
@@ -0,0 +1,9 @@
+package com.reflectoring.lombok.processor;
+
+import java.nio.file.Path;
+import java.util.List;
+
+public interface DataProcessor {
+ void dataProcess();
+ List readFromFile(Path path);
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/processor/FileDataProcessor.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/processor/FileDataProcessor.java
new file mode 100644
index 000000000..85b889cad
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/processor/FileDataProcessor.java
@@ -0,0 +1,48 @@
+package com.reflectoring.lombok.processor;
+
+import lombok.SneakyThrows;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+import java.util.List;
+
+public class FileDataProcessor implements DataProcessor {
+
+ public static final Logger log = LoggerFactory.getLogger(FileDataProcessor.class);
+
+ @Override
+ public void dataProcess() {
+ String data = processFile();
+ log.info("File data: {}", data);
+ processData(data);
+ }
+
+ @SneakyThrows(IOException.class)
+ public List readFromFile(Path path) {
+ return Files.readAllLines(path);
+ }
+
+ @SneakyThrows(IOException.class)
+ private String processFile() {
+ File file = new ClassPathResource("sample1.txt").getFile();
+ log.info("Check if file exists: {}", file.exists());
+ return FileUtils.readFileToString(file, "UTF-8");
+ }
+
+ @SneakyThrows(DateTimeParseException.class)
+ private void processData(String data) {
+ LocalDate localDt = LocalDate.parse(data);
+ log.info("Date: {}", localDt);
+ }
+
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/repository/BookRepository.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/repository/BookRepository.java
new file mode 100644
index 000000000..758b05713
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/repository/BookRepository.java
@@ -0,0 +1,9 @@
+package com.reflectoring.lombok.repository;
+
+import com.reflectoring.lombok.model.persistence.Book;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface BookRepository extends JpaRepository {
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/repository/PublisherRepository.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/repository/PublisherRepository.java
new file mode 100644
index 000000000..01902a5a1
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/repository/PublisherRepository.java
@@ -0,0 +1,9 @@
+package com.reflectoring.lombok.repository;
+
+import com.reflectoring.lombok.model.persistence.Publisher;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PublisherRepository extends JpaRepository {
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/service/SneakyThrowsService.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/service/SneakyThrowsService.java
new file mode 100644
index 000000000..d66934c59
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/lombok/service/SneakyThrowsService.java
@@ -0,0 +1,42 @@
+package com.reflectoring.lombok.service;
+
+import com.reflectoring.lombok.processor.DataProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class SneakyThrowsService {
+
+ private static final Logger log = LoggerFactory.getLogger(SneakyThrowsService.class);
+
+ private final DataProcessor fileDataProcessor;
+
+ public SneakyThrowsService(DataProcessor fileDataProcessor) {
+ this.fileDataProcessor = fileDataProcessor;
+ }
+
+ public void getFileData() {
+ try {
+ fileDataProcessor.dataProcess();
+ } catch (Exception ex) {
+ log.error("Error reading from file", ex);
+ throw ex;
+ }
+ }
+
+ // @SneakyThrows simplifies lambda usage
+ public List> readFileData() {
+ List paths = List.of("/file1", "/file2");
+ return paths.stream()
+ .map(Paths::get)
+ .map(fileDataProcessor::readFromFile)
+ .collect(Collectors.toList());
+ }
+
+
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/tryme/Example.java b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/tryme/Example.java
new file mode 100644
index 000000000..67c68743f
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/java/com/reflectoring/tryme/Example.java
@@ -0,0 +1,21 @@
+package com.reflectoring.tryme;
+
+import com.reflectoring.lombok.model.Account;
+import com.reflectoring.lombok.model.Job;
+import com.reflectoring.lombok.model.JobType;
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+public class Example {
+
+ public static void main(String[] args) {
+ Account account = Account.builder().acctName("Savings")
+ .acctNo("A001090")
+ .build();
+ log.info("Account details : {}", account);
+
+ Job job = Job.builder().id("5678").jobType(JobType.CARPENTER).build();
+ log.info("Job details : {}", job);
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/resources/application.yaml b/core-java/lombok/be-informed-with-lombok/src/main/resources/application.yaml
new file mode 100644
index 000000000..d1f16fc41
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/resources/application.yaml
@@ -0,0 +1,11 @@
+spring:
+ datasource:
+ driver-class-name: org.hsqldb.jdbc.JDBCDriver
+ url: jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1
+ username: sa
+ password:
+
+ jpa:
+ defer-datasource-initialization: true
+ hibernate:
+ ddl-auto: create
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/resources/sample.txt b/core-java/lombok/be-informed-with-lombok/src/main/resources/sample.txt
new file mode 100644
index 000000000..b4f8b3659
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/resources/sample.txt
@@ -0,0 +1 @@
+This is a sample file
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/src/main/resources/schema.sql b/core-java/lombok/be-informed-with-lombok/src/main/resources/schema.sql
new file mode 100644
index 000000000..b41ef3628
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/main/resources/schema.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS BOOK( ID INTEGER NOT NULL, NAME VARCHAR(45) NOT NULL, PRIMARY KEY(ID));
+
+CREATE TABLE IF NOT EXISTS PUBLISHER_BOOK( BOOK_ID INTEGER NOT NULL, PUBLISHER_ID INTEGER NOT NULL, PRIMARY KEY(BOOK_ID, PUBLISHER_ID));
+
+CREATE TABLE IF NOT EXISTS PUBLISHER( ID INTEGER NOT NULL, NAME VARCHAR(45) NOT NULL, PRIMARY KEY(ID));
+
+CREATE SEQUENCE IF NOT EXISTS hibernate_sequence START WITH 1 INCREMENT BY 1;
diff --git a/core-java/lombok/be-informed-with-lombok/src/test/java/com/reflectoring/lombok/PersistenceTest.java b/core-java/lombok/be-informed-with-lombok/src/test/java/com/reflectoring/lombok/PersistenceTest.java
new file mode 100644
index 000000000..fd57fb4ff
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/test/java/com/reflectoring/lombok/PersistenceTest.java
@@ -0,0 +1,27 @@
+package com.reflectoring.lombok;
+
+import com.reflectoring.lombok.repository.BookRepository;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import javax.transaction.Transactional;
+
+@SpringBootTest(classes = Application.class)
+@Transactional
+public class PersistenceTest {
+
+ private static Logger log = LoggerFactory.getLogger(PersistenceTest.class);
+
+ @Autowired
+ private BookRepository bookRepository;
+
+ @Test
+ public void loadData() {
+ log.info("Books : {}", bookRepository.findAll());
+ Assertions.assertEquals(2, bookRepository.count());
+ }
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/test/java/com/reflectoring/lombok/controller/SneakyThrowsControllerTest.java b/core-java/lombok/be-informed-with-lombok/src/test/java/com/reflectoring/lombok/controller/SneakyThrowsControllerTest.java
new file mode 100644
index 000000000..67a986d18
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/test/java/com/reflectoring/lombok/controller/SneakyThrowsControllerTest.java
@@ -0,0 +1,47 @@
+package com.reflectoring.lombok.controller;
+
+import com.reflectoring.lombok.service.SneakyThrowsService;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.RequestEntity;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.net.URI;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ActiveProfiles("test")
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class SneakyThrowsControllerTest {
+
+ @Autowired
+ private TestRestTemplate testRestTemplate;
+
+ @LocalServerPort
+ private int port;
+
+ @Autowired
+ private SneakyThrowsService sneakyThrowsService;
+
+ @Test
+ public void contextLoads() {
+ assertThat(sneakyThrowsService).isNotNull();
+ }
+
+ @Test
+ public void getFileData_errorResponse() throws Exception {
+
+ Map apiResponse = testRestTemplate.exchange(
+ RequestEntity.get(new URI("http://localhost:"+port+"/api/sneakyThrows")).build(),
+ new ParameterizedTypeReference