diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f946566d0..4c674153d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,16 +1,52 @@
name: CI
-on: [push]
+on:
+ push:
+ paths-ignore:
+ - '**/*.md'
+ pull_request:
+ paths-ignore:
+ - '**/*.md'
jobs:
+
build:
+
runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ # The MODULE environment variable is evaluated in build-all.sh to run a subset
+ # of the builds. This way, multiple modules can be built in parallel.
+ module: [ "module1", "module2", "module3", "module4", "module5", "module6", "module7" ]
+
steps:
+
- name: "Checkout sources"
uses: actions/checkout@v1
+
- name: "Setup Java"
uses: actions/setup-java@v1
with:
java-version: 13
- - name: "Build all modules"
- run: chmod 755 build-all.sh && ./build-all.sh
+
+ - name: "Build module ${{ matrix.module }}"
+ env:
+ MODULE: ${{ matrix.module }}
+ # We don't actually need AWS credentials in the tests, but LocalStack
+ # complains if they're not there, so we add dummies to the environment.
+ AWS_ACCESS_KEY_ID: dummy
+ AWS_SECRET_ACCESS_KEY: dummy
+ AWS_REGION: us-east-1
+ run: |
+ chmod 755 build-all.sh && ./build-all.sh $MODULE
+
+ - name: "Zip build reports"
+ if: failure()
+ run: zip -r reports.zip **/**/build/reports
+
+ - uses: actions/upload-artifact@v1
+ name: "Upload build reports"
+ if: failure()
+ with:
+ name: reports
+ path: reports.zip
diff --git a/.gitignore b/.gitignore
index 07f8b1e87..ea75cffcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
**/.idea/
**/*.iml
+**/.DS_Store
+**/.terraform
diff --git a/README.md b/README.md
index 7317bbbe0..4bd623602 100644
--- a/README.md
+++ b/README.md
@@ -5,17 +5,4 @@
This repo contains example projects which show how to use different (not only) Java technologies.
The examples are usually accompanied by a blog post on [https://reflectoring.io](https://reflectoring.io).
-See the READMEs in each subdirectory of this repo for more information on each module.
-
-## Java Modules
-All Java modules require **Java 11** to compile and run.
-
-### Building with Gradle
-
-Each module should be an independent build and can be built by calling `./gradlew clean build` in the module directory.
-
-All modules are listed in [build-all.sh](build-all.sh) to run in the CI pipeline.
-
-### Non-Java Modules
-
-Some folders contain non-Java projects. For those, refer to the README within the module folder.
+See the READMEs in each subdirectory of this repo for more information on each module.
\ No newline at end of file
diff --git a/apache-http-client/.gitignore b/apache-http-client/.gitignore
new file mode 100644
index 000000000..3aff99610
--- /dev/null
+++ b/apache-http-client/.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/apache-http-client/.mvn/wrapper/MavenWrapperDownloader.java b/apache-http-client/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/apache-http-client/.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/apache-http-client/.mvn/wrapper/maven-wrapper.jar b/apache-http-client/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/apache-http-client/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/apache-http-client/.mvn/wrapper/maven-wrapper.properties b/apache-http-client/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/apache-http-client/.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/apache-http-client/README.md b/apache-http-client/README.md
new file mode 100644
index 000000000..d9eb036ca
--- /dev/null
+++ b/apache-http-client/README.md
@@ -0,0 +1,3 @@
+# Related Blog Posts
+
+* [Create a Http Client with Apache Http Client](https://reflectoring.io/create-a-http-client-with-apache-http-client/)
diff --git a/apache-http-client/mvnw b/apache-http-client/mvnw
new file mode 100644
index 000000000..41c0f0c23
--- /dev/null
+++ b/apache-http-client/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/apache-http-client/mvnw.cmd b/apache-http-client/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/apache-http-client/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/apache-http-client/pom.xml b/apache-http-client/pom.xml
new file mode 100644
index 000000000..0861eb716
--- /dev/null
+++ b/apache-http-client/pom.xml
@@ -0,0 +1,156 @@
+
+ 4.0.0
+ com.reflectoring
+ apache-http-client
+ 0.0.1-SNAPSHOT
+ Apache Http Client
+ https://reflectoring.io
+
+
+ UTF-8
+ 17
+ 17
+ 5.10.2
+ 5.3.1
+
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ ${apache-http-client.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5-cache
+ ${apache-http-client.version}
+
+
+
+
+ org.apache.httpcomponents.core5
+ httpcore5-reactive
+ 5.2.4
+
+
+
+
+ io.reactivex.rxjava3
+ rxjava
+ 3.1.8
+
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.28
+
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ 2.0.12
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.5.0
+ test
+
+
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.9
+
+
+
+
+ org.json
+ json
+ 20240205
+
+
+
+ org.apache.commons
+ commons-configuration2
+ 2.9.0
+
+
+
+
+ commons-beanutils
+ commons-beanutils
+ 1.9.4
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.16.0
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.15.3
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit-jupiter.version}
+ pom
+ import
+
+
+
+
\ No newline at end of file
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/RequestProcessingException.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/RequestProcessingException.java
new file mode 100644
index 000000000..a58cbacf3
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/RequestProcessingException.java
@@ -0,0 +1,14 @@
+package io.refactoring.http5.client.example;
+
+/** Represents an exception for HTTP request processing. */
+public class RequestProcessingException extends RuntimeException {
+ /**
+ * Construction.
+ *
+ * @param message error message
+ * @param cause source exception
+ */
+ public RequestProcessingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/CustomHttpResponseCallback.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/CustomHttpResponseCallback.java
new file mode 100644
index 000000000..85265c6ae
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/CustomHttpResponseCallback.java
@@ -0,0 +1,56 @@
+package io.refactoring.http5.client.example.async.helper;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import java.util.concurrent.CountDownLatch;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.message.StatusLine;
+
+/** The http response callback. */
+@Slf4j
+public class CustomHttpResponseCallback implements FutureCallback {
+ /** The Http get request. */
+ private final SimpleHttpRequest httpRequest;
+
+ /** The Error message. */
+ private final String errorMessage;
+
+ /** The Latch. */
+ private final CountDownLatch latch;
+
+ /**
+ * Instantiates a new pipelined http response callback.
+ *
+ * @param httpRequest the http request
+ * @param errorMessage the error message
+ * @param latch the latch
+ */
+ public CustomHttpResponseCallback(
+ SimpleHttpRequest httpRequest, String errorMessage, CountDownLatch latch) {
+ this.httpRequest = httpRequest;
+ this.errorMessage = errorMessage;
+ this.latch = latch;
+ }
+
+ @Override
+ public void completed(final SimpleHttpResponse response) {
+ latch.countDown();
+ log.debug(httpRequest + "->" + new StatusLine(response));
+ log.debug("Got response: {}", response.getBody());
+ }
+
+ @Override
+ public void failed(final Exception ex) {
+ latch.countDown();
+ log.error(httpRequest + "->" + ex);
+ throw new RequestProcessingException(errorMessage, ex);
+ }
+
+ @Override
+ public void cancelled() {
+ latch.countDown();
+ log.debug(httpRequest + " cancelled");
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleCharResponseConsumer.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleCharResponseConsumer.java
new file mode 100644
index 000000000..0ee6b634e
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleCharResponseConsumer.java
@@ -0,0 +1,73 @@
+package io.refactoring.http5.client.example.async.helper;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.message.StatusLine;
+
+/** The Simple http character stream consumer. */
+@Slf4j
+public class SimpleCharResponseConsumer extends AbstractCharResponseConsumer {
+ /** The Http get request. */
+ private final SimpleHttpRequest httpRequest;
+
+ private final StringBuilder responseBuilder = new StringBuilder();
+
+ /** The Error message. */
+ private final String errorMessage;
+
+ /**
+ * Instantiates a new Simple http response callback.
+ *
+ * @param httpRequest the http request
+ * @param errorMessage the error message
+ */
+ public SimpleCharResponseConsumer(SimpleHttpRequest httpRequest, String errorMessage) {
+ this.httpRequest = httpRequest;
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ protected void start(HttpResponse httpResponse, ContentType contentType)
+ throws HttpException, IOException {
+ log.debug(httpRequest + "->" + new StatusLine(httpResponse));
+ responseBuilder.setLength(0);
+ }
+
+ @Override
+ protected SimpleHttpResponse buildResult() throws IOException {
+ return SimpleHttpResponse.create(HttpStatus.SC_OK, responseBuilder.toString());
+ }
+
+ @Override
+ protected int capacityIncrement() {
+ return 0;
+ }
+
+ @Override
+ protected void data(CharBuffer src, boolean endOfStream) throws IOException {
+ while (src.hasRemaining()) {
+ responseBuilder.append(src.get());
+ }
+ if (endOfStream) {
+ log.debug(responseBuilder.toString());
+ }
+ }
+
+ @Override
+ public void failed(Exception ex) {
+ log.error(httpRequest + "->" + ex);
+ throw new RequestProcessingException(errorMessage, ex);
+ }
+
+ @Override
+ public void releaseResources() {}
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleHttpResponseCallback.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleHttpResponseCallback.java
new file mode 100644
index 000000000..5db683d79
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleHttpResponseCallback.java
@@ -0,0 +1,49 @@
+package io.refactoring.http5.client.example.async.helper;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.message.StatusLine;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** The Simple http response callback. */
+@Slf4j
+public class SimpleHttpResponseCallback implements FutureCallback {
+ /** The Http get request. */
+ private final SimpleHttpRequest httpRequest;
+
+ /** The Error message. */
+ private final String errorMessage;
+
+ /**
+ * Instantiates a new Simple http response callback.
+ *
+ * @param httpRequest the http request
+ * @param errorMessage the error message
+ */
+ public SimpleHttpResponseCallback(SimpleHttpRequest httpRequest, String errorMessage) {
+ this.httpRequest = httpRequest;
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public void completed(final SimpleHttpResponse response) {
+ log.debug(httpRequest + "->" + new StatusLine(response));
+ log.debug("Got response: {}", response.getBody());
+ }
+
+ @Override
+ public void failed(final Exception ex) {
+ log.error(httpRequest + "->" + ex);
+ throw new RequestProcessingException(errorMessage, ex);
+ }
+
+ @Override
+ public void cancelled() {
+ log.debug(httpRequest + " cancelled");
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelper.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelper.java
new file mode 100644
index 000000000..b70a51cd4
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelper.java
@@ -0,0 +1,623 @@
+package io.refactoring.http5.client.example.async.helper;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.Observable;
+import io.refactoring.http5.client.example.RequestProcessingException;
+import io.refactoring.http5.client.example.config.interceptor.UserResponseAsyncExecChainHandler;
+import io.refactoring.http5.client.example.helper.BaseHttpRequestHelper;
+import io.refactoring.http5.client.example.model.User;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.concurrent.*;
+import javax.net.ssl.SSLContext;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.async.methods.*;
+import org.apache.hc.client5.http.config.TlsConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
+import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
+import org.apache.hc.core5.http.*;
+import org.apache.hc.core5.http.config.Http1Config;
+import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
+import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.net.URIBuilder;
+import org.apache.hc.core5.reactive.ReactiveEntityProducer;
+import org.apache.hc.core5.reactive.ReactiveResponseConsumer;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.ssl.SSLContexts;
+import org.apache.hc.core5.util.Timeout;
+import org.reactivestreams.Publisher;
+
+/** Handles HTTP requests for user entities. It uses built in types for HTTP processing. */
+@Slf4j
+public class UserAsyncHttpRequestHelper extends BaseHttpRequestHelper {
+
+ private static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private CloseableHttpAsyncClient httpClient;
+
+ private MinimalHttpAsyncClient minimalHttp1Client;
+ private MinimalHttpAsyncClient minimalHttp2Client;
+ private CloseableHttpAsyncClient httpAsyncInterceptingClient;
+
+ /** Starts http async client. */
+ public void startHttpAsyncClient() {
+ if (httpClient == null) {
+ try {
+ PoolingAsyncClientConnectionManager cm =
+ PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(getTlsStrategy())
+ .build();
+ IOReactorConfig ioReactorConfig =
+ IOReactorConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build();
+ httpClient =
+ HttpAsyncClients.custom()
+ .setIOReactorConfig(ioReactorConfig)
+ .setConnectionManager(cm)
+ .build();
+ httpClient.start();
+ log.debug("Started HTTP async client.");
+ } catch (Exception e) {
+ String errorMsg = "Failed to start HTTP async client.";
+ log.error(errorMsg, e);
+ throw new RuntimeException(errorMsg, e);
+ }
+ }
+ }
+
+ private TlsStrategy getTlsStrategy()
+ throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
+ // Trust standard CA and those trusted by our custom strategy
+ SSLContext sslContext =
+ SSLContexts.custom()
+ // Custom TrustStrategy implementations are intended for verification
+ // of certificates whose CA is not trusted by the system, and where specifying
+ // a custom truststore containing the certificate chain is not an option.
+ .loadTrustMaterial(
+ (chain, authType) -> {
+ // Please note that validation of the server certificate without validation
+ // of the entire certificate chain in this example is preferred to completely
+ // disabling trust verification, however, this still potentially allows
+ // for man-in-the-middle attacks.
+ X509Certificate cert = chain[0];
+ log.warn(
+ "Bypassing SSL certificate validation for {}",
+ cert.getSubjectX500Principal().getName());
+ return true;
+ })
+ .build();
+
+ return ClientTlsStrategyBuilder.create().setSslContext(sslContext).build();
+ }
+
+ /**
+ * Starts http 1 async client.
+ *
+ * @return the minimal http async client
+ */
+ public MinimalHttpAsyncClient startMinimalHttp1AsyncClient() {
+ if (minimalHttp1Client == null) {
+ minimalHttp1Client = startMinimalHttpAsyncClient(HttpVersionPolicy.FORCE_HTTP_1);
+ }
+ return minimalHttp1Client;
+ }
+
+ /**
+ * Starts http 2 async client.
+ *
+ * @return minimal http async client
+ */
+ public MinimalHttpAsyncClient startMinimalHttp2AsyncClient() {
+ if (minimalHttp2Client == null) {
+ minimalHttp2Client = startMinimalHttpAsyncClient(HttpVersionPolicy.FORCE_HTTP_2);
+ }
+ return minimalHttp2Client;
+ }
+
+ /**
+ * Starts http async client.
+ *
+ * @return minimal Http client;
+ */
+ private MinimalHttpAsyncClient startMinimalHttpAsyncClient(HttpVersionPolicy httpVersionPolicy) {
+ try {
+ MinimalHttpAsyncClient minimalHttpClient =
+ HttpAsyncClients.createMinimal(
+ H2Config.DEFAULT,
+ Http1Config.DEFAULT,
+ IOReactorConfig.DEFAULT,
+ PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(getTlsStrategy())
+ .setDefaultTlsConfig(
+ TlsConfig.custom().setVersionPolicy(httpVersionPolicy).build())
+ .build());
+ minimalHttpClient.start();
+ log.debug("Started minimal HTTP async client for {}.", httpVersionPolicy);
+ return minimalHttpClient;
+ } catch (Exception e) {
+ String errorMsg = "Failed to start minimal HTTP async client.";
+ log.error(errorMsg, e);
+ throw new RuntimeException(errorMsg, e);
+ }
+ }
+
+ /**
+ * Starts http async intercepting client.
+ *
+ * @return closeable http async client
+ */
+ public CloseableHttpAsyncClient startHttpAsyncInterceptingClient() {
+ try {
+ if (httpAsyncInterceptingClient == null) {
+ PoolingAsyncClientConnectionManager cm =
+ PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(getTlsStrategy())
+ .build();
+ IOReactorConfig ioReactorConfig =
+ IOReactorConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build();
+ httpAsyncInterceptingClient =
+ HttpAsyncClients.custom()
+ .setIOReactorConfig(ioReactorConfig)
+ .setConnectionManager(cm)
+ .addExecInterceptorFirst("custom", new UserResponseAsyncExecChainHandler())
+ .build();
+ httpAsyncInterceptingClient.start();
+ log.debug("Started HTTP async client with requests interceptors.");
+ }
+ return httpAsyncInterceptingClient;
+ } catch (Exception e) {
+ String errorMsg = "Failed to start HTTP async client.";
+ log.error(errorMsg, e);
+ throw new RuntimeException(errorMsg, e);
+ }
+ }
+
+ /** Stops http async client. */
+ public void stopHttpAsyncClient() {
+ if (httpClient != null) {
+ log.info("Shutting down http async client.");
+ httpClient.close(CloseMode.GRACEFUL);
+ httpClient = null;
+ }
+ }
+
+ /**
+ * Stops minimal http async client.
+ *
+ * @param minimalHttpClient the minimal http client
+ */
+ public void stopMinimalHttpAsyncClient(MinimalHttpAsyncClient minimalHttpClient) {
+ if (minimalHttpClient != null) {
+ log.info("Shutting down minimal http async client.");
+ minimalHttpClient.close(CloseMode.GRACEFUL);
+ minimalHttpClient = null;
+ }
+ }
+
+ /**
+ * Gets all users for given ids using async callback.
+ *
+ * @param userIdList user id list
+ * @param delayInSec the delay in seconds by which server will send the response
+ * @return response if user is found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public Map getUserWithCallback(List userIdList, int delayInSec)
+ throws RequestProcessingException {
+ Objects.requireNonNull(httpClient, "Make sure that HTTP Async client is started.");
+ Map userResponseMap = new HashMap<>();
+ Map> futuresMap = new HashMap<>();
+ for (String userId : userIdList) {
+ try {
+ // Create request
+ HttpHost httpHost = HttpHost.create("https://reqres.in");
+ URI uri = new URIBuilder("/api/users/" + userId + "?delay=" + delayInSec).build();
+ SimpleHttpRequest httpGetRequest =
+ SimpleRequestBuilder.get().setHttpHost(httpHost).setPath(uri.getPath()).build();
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ Future future =
+ httpClient.execute(
+ SimpleRequestProducer.create(httpGetRequest),
+ SimpleResponseConsumer.create(),
+ new SimpleHttpResponseCallback(
+ httpGetRequest,
+ MessageFormat.format("Failed to get user for ID: {0}", userId)));
+ futuresMap.put(userId, future);
+ } catch (Exception e) {
+ String message = MessageFormat.format("Failed to get user for ID: {0}", userId);
+ log.error(message, e);
+ userResponseMap.put(userId, message);
+ }
+ }
+
+ log.debug("Got {} futures.", futuresMap.size());
+
+ for (Map.Entry> futureEntry : futuresMap.entrySet()) {
+ String userId = futureEntry.getKey();
+ try {
+ userResponseMap.put(userId, futureEntry.getValue().get().getBodyText());
+ } catch (Exception e) {
+ String message = MessageFormat.format("Failed to get user for ID: {0}", userId);
+ log.error(message, e);
+ userResponseMap.put(userId, message);
+ }
+ }
+
+ return userResponseMap;
+ }
+
+ /**
+ * Gets all users for given ids using streams.
+ *
+ * @param userIdList user id list
+ * @param delayInSec the delay in seconds by which server will send the response
+ * @return response if user is found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public Map getUserWithStreams(List userIdList, int delayInSec)
+ throws RequestProcessingException {
+ Objects.requireNonNull(httpClient, "Make sure that HTTP Async client is started.");
+ Map userResponseMap = new HashMap<>();
+ Map> futuresMap = new HashMap<>();
+ for (Long userId : userIdList) {
+ try {
+ // Create request
+ HttpHost httpHost = HttpHost.create("https://reqres.in");
+ URI uri = new URIBuilder("/api/users/" + userId + "?delay=" + delayInSec).build();
+ SimpleHttpRequest httpGetRequest =
+ SimpleRequestBuilder.get().setHttpHost(httpHost).setPath(uri.getPath()).build();
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ Future future =
+ httpClient.execute(
+ new BasicRequestProducer(httpGetRequest, null),
+ new SimpleCharResponseConsumer(
+ httpGetRequest, MessageFormat.format("Failed to get user for ID: {0}", userId)),
+ null);
+ futuresMap.put(userId, future);
+ } catch (Exception e) {
+ String message = MessageFormat.format("Failed to get user for ID: {0}", userId);
+ log.error(message, e);
+ userResponseMap.put(userId, message);
+ }
+ }
+
+ log.debug("Got {} futures.", futuresMap.size());
+ for (Map.Entry> futureEntry : futuresMap.entrySet()) {
+ Long userId = futureEntry.getKey();
+ try {
+ userResponseMap.put(userId, futureEntry.getValue().get().getBodyText());
+ } catch (Exception e) {
+ String message = MessageFormat.format("Failed to get user for ID: {0}", userId);
+ log.error(message, e);
+ userResponseMap.put(userId, message);
+ }
+ }
+
+ return userResponseMap;
+ }
+
+ /**
+ * Gets all users for given ids using pipelining.
+ *
+ * @param minimalHttpClient the minimal http client
+ * @param userIdList user id list
+ * @param delayInSec the delay in seconds by which server will send the response
+ * @param scheme the scheme
+ * @param hostname the hostname
+ * @return response if user is found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public Map getUserWithPipelining(
+ MinimalHttpAsyncClient minimalHttpClient,
+ List userIdList,
+ int delayInSec,
+ String scheme,
+ String hostname)
+ throws RequestProcessingException {
+ return getUserWithParallelRequests(minimalHttpClient, userIdList, delayInSec, scheme, hostname);
+ }
+
+ /**
+ * Gets all users for given ids using multiplexing.
+ *
+ * @param minimalHttpClient the minimal http client
+ * @param userIdList user id list
+ * @param delayInSec the delay in seconds by which server will send the response
+ * @param scheme the scheme
+ * @param hostname the hostname
+ * @return response if user is found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public Map getUserWithMultiplexing(
+ MinimalHttpAsyncClient minimalHttpClient,
+ List userIdList,
+ int delayInSec,
+ String scheme,
+ String hostname)
+ throws RequestProcessingException {
+ return getUserWithParallelRequests(minimalHttpClient, userIdList, delayInSec, scheme, hostname);
+ }
+
+ private Map getUserWithParallelRequests(
+ MinimalHttpAsyncClient minimalHttpClient,
+ List userIdList,
+ int delayInSec,
+ String scheme,
+ String hostname)
+ throws RequestProcessingException {
+
+ Objects.requireNonNull(
+ minimalHttpClient, "Make sure that minimal HTTP Async client is started.");
+ Map userResponseMap = new HashMap<>();
+ Map> futuresMap = new LinkedHashMap<>();
+ AsyncClientEndpoint endpoint = null;
+ Long userId = null;
+
+ try {
+ HttpHost httpHost = new HttpHost(scheme, hostname);
+ Future leaseFuture = minimalHttpClient.lease(httpHost, null);
+ endpoint = leaseFuture.get(30, TimeUnit.SECONDS);
+ CountDownLatch latch = new CountDownLatch(userIdList.size());
+
+ for (Long currentUserId : userIdList) {
+ userId = currentUserId;
+ // Create request
+ URI uri = new URIBuilder("/api/users/" + userId + "?delay=" + delayInSec).build();
+ SimpleHttpRequest httpGetRequest =
+ SimpleRequestBuilder.get().setHttpHost(httpHost).setPath(uri.getPath()).build();
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ Future future =
+ minimalHttpClient.execute(
+ SimpleRequestProducer.create(httpGetRequest),
+ SimpleResponseConsumer.create(),
+ new CustomHttpResponseCallback(
+ httpGetRequest,
+ MessageFormat.format("Failed to get user for ID: {0}", userId),
+ latch));
+ futuresMap.put(userId, future);
+ }
+
+ latch.await();
+ } catch (RequestProcessingException e) {
+ userResponseMap.put(userId, e.getMessage());
+ } catch (Exception e) {
+ if (userId != null) {
+ String message = MessageFormat.format("Failed to get user for ID: {0}", userId);
+ log.error(message, e);
+ userResponseMap.put(userId, message);
+ } else {
+ throw new RequestProcessingException("Failed to process request.", e);
+ }
+ } finally {
+ if (endpoint != null) {
+ endpoint.releaseAndReuse();
+ }
+ }
+
+ handleFutureResults(futuresMap, userResponseMap);
+
+ return userResponseMap;
+ }
+
+ private void handleFutureResults(
+ Map> futuresMap, Map userResponseMap) {
+ log.debug("Got {} futures.", futuresMap.size());
+
+ for (Map.Entry> futureEntry : futuresMap.entrySet()) {
+ Long currentUserId = futureEntry.getKey();
+ try {
+ userResponseMap.put(currentUserId, futureEntry.getValue().get().getBodyText());
+ } catch (Exception e) {
+ String message;
+ if (e.getCause() instanceof ConnectionClosedException) {
+ message = "Server does not support HTTP/2 multiplexing.";
+ } else {
+ message = MessageFormat.format("Failed to get user for ID: {0}", currentUserId);
+ }
+ log.error(message, e);
+ userResponseMap.put(currentUserId, message);
+ }
+ }
+ }
+
+ /**
+ * Execute requests with interceptors.
+ *
+ * @param closeableHttpAsyncClient the closeable http async client
+ * @param userId the user id
+ * @param count the request execution count
+ * @param baseNumber the base number
+ * @return the map
+ * @throws RequestProcessingException the request processing exception
+ */
+ public Map executeRequestsWithInterceptors(
+ CloseableHttpAsyncClient closeableHttpAsyncClient, Long userId, int count, int baseNumber)
+ throws RequestProcessingException {
+ Objects.requireNonNull(
+ closeableHttpAsyncClient, "Make sure that HTTP Async client is started.");
+ Map userResponseMap = new HashMap<>();
+ Map> futuresMap = new LinkedHashMap<>();
+
+ try {
+ HttpHost httpHost = HttpHost.create("https://reqres.in");
+ URI uri = new URIBuilder("/api/users/" + userId).build();
+ String path = uri.getPath();
+ SimpleHttpRequest httpGetRequest =
+ SimpleRequestBuilder.get()
+ .setHttpHost(httpHost)
+ .setPath(path)
+ .addHeader("x-base-number", String.valueOf(baseNumber))
+ .build();
+ for (int i = 0; i < count; i++) {
+ try {
+ Future future =
+ executeInterceptorRequest(closeableHttpAsyncClient, httpGetRequest, i, httpHost);
+ futuresMap.put(i, future);
+ } catch (RequestProcessingException e) {
+ userResponseMap.put(i, e.getMessage());
+ }
+ }
+ } catch (Exception e) {
+ String message = MessageFormat.format("Failed to get user for ID: {0}", userId);
+ log.error(message, e);
+ throw new RequestProcessingException(message, e);
+ }
+
+ handleInterceptorFutureResults(futuresMap, userResponseMap);
+
+ return userResponseMap;
+ }
+
+ private Future executeInterceptorRequest(
+ CloseableHttpAsyncClient closeableHttpAsyncClient,
+ SimpleHttpRequest httpGetRequest,
+ int i,
+ HttpHost httpHost)
+ throws URISyntaxException {
+ // Update request
+ httpGetRequest.removeHeaders("x-req-exec-number");
+ httpGetRequest.addHeader("x-req-exec-number", String.valueOf(i));
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ return closeableHttpAsyncClient.execute(
+ httpGetRequest, new SimpleHttpResponseCallback(httpGetRequest, ""));
+ }
+
+ private void handleInterceptorFutureResults(
+ Map> futuresMap, Map userResponseMap) {
+ log.debug("Got {} futures.", futuresMap.size());
+
+ for (Map.Entry> futureEntry : futuresMap.entrySet()) {
+ Integer currentRequestId = futureEntry.getKey();
+ try {
+ userResponseMap.put(currentRequestId, futureEntry.getValue().get().getBodyText());
+ } catch (Exception e) {
+ String message =
+ MessageFormat.format("Failed to get user for request id: {0}", currentRequestId);
+ log.error(message, e);
+ userResponseMap.put(currentRequestId, message);
+ }
+ }
+ }
+
+ /**
+ * Creates user with reactive processing.
+ *
+ * @param minimalHttpClient the minimal http client
+ * @param userName the username
+ * @param userJob the user job
+ * @param scheme the scheme
+ * @param hostname the hostname
+ * @return the user with reactive processing
+ * @throws RequestProcessingException the request processing exception
+ */
+ public User createUserWithReactiveProcessing(
+ MinimalHttpAsyncClient minimalHttpClient,
+ String userName,
+ String userJob,
+ String scheme,
+ String hostname)
+ throws RequestProcessingException {
+ try {
+ HttpHost httpHost = new HttpHost(scheme, hostname);
+ URI uri = new URIBuilder(httpHost.toURI() + "/api/users/").build();
+ String payloadStr = preparePayload(userName, userJob);
+ ReactiveResponseConsumer consumer = new ReactiveResponseConsumer();
+ Future requestFuture = executeRequest(minimalHttpClient, consumer, uri, payloadStr);
+
+ Message> streamingResponse =
+ consumer.getResponseFuture().get();
+ printHeaders(streamingResponse);
+ return prepareResult(streamingResponse, requestFuture);
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to create user. Error: " + e.getMessage(), e);
+ }
+ }
+
+ private void printHeaders(Message> streamingResponse) {
+ log.debug("Head: {}", streamingResponse.getHead());
+ for (Header header : streamingResponse.getHead().getHeaders()) {
+ log.debug("Header : {}", header);
+ }
+ }
+
+ private String preparePayload(String userName, String userJob) throws JsonProcessingException {
+ Map payload = new HashMap<>();
+ payload.put("name", userName);
+ payload.put("job", userJob);
+ return OBJECT_MAPPER.writeValueAsString(payload);
+ }
+
+ private User prepareResult(
+ Message> streamingResponse, Future requestFuture)
+ throws InterruptedException, ExecutionException, TimeoutException, JsonProcessingException {
+ StringBuilder result = new StringBuilder();
+ Observable.fromPublisher(streamingResponse.getBody())
+ .map(
+ byteBuffer -> {
+ byte[] bytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(bytes);
+ return new String(bytes);
+ })
+ .materialize()
+ .forEach(
+ stringNotification -> {
+ String value = stringNotification.getValue();
+ if (value != null) {
+ result.append(value);
+ }
+ });
+
+ requestFuture.get(1, TimeUnit.MINUTES);
+ return OBJECT_MAPPER.readerFor(User.class).readValue(result.toString());
+ }
+
+ private Future executeRequest(
+ MinimalHttpAsyncClient minimalHttpClient,
+ ReactiveResponseConsumer consumer,
+ URI uri,
+ String payloadStr) {
+ byte[] bs = payloadStr.getBytes(StandardCharsets.UTF_8);
+ ReactiveEntityProducer reactiveEntityProducer =
+ new ReactiveEntityProducer(
+ Flowable.just(ByteBuffer.wrap(bs)), bs.length, ContentType.TEXT_PLAIN, null);
+
+ return minimalHttpClient.execute(
+ new BasicRequestProducer("POST", uri, reactiveEntityProducer), consumer, null);
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/handler/DataObjectResponseHandler.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/handler/DataObjectResponseHandler.java
new file mode 100644
index 000000000..ccd56a530
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/handler/DataObjectResponseHandler.java
@@ -0,0 +1,47 @@
+package io.refactoring.http5.client.example.classic.handler;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ParseException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+/**
+ * Handles response for data objects.
+ *
+ * @param type of data object
+ */
+@Slf4j
+public class DataObjectResponseHandler extends AbstractHttpClientResponseHandler {
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @NonNull private final Class realType;
+
+ /**
+ * Construction.
+ *
+ * @param realType the type of data object
+ */
+ public DataObjectResponseHandler(@NonNull final Class realType) {
+ this.realType = realType;
+ }
+
+ /**
+ * Represents ResponseHandler for converting the response entity into POJO instance.
+ *
+ * @param type of data object
+ */
+ @Override
+ public T handleEntity(HttpEntity httpEntity) throws IOException {
+
+ try {
+ return objectMapper.readValue(EntityUtils.toString(httpEntity), realType);
+ } catch (ParseException e) {
+ throw new ClientProtocolException(e);
+ }
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/helper/UserSimpleHttpRequestHelper.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/helper/UserSimpleHttpRequestHelper.java
new file mode 100644
index 000000000..433e33965
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/helper/UserSimpleHttpRequestHelper.java
@@ -0,0 +1,342 @@
+package io.refactoring.http5.client.example.classic.helper;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import io.refactoring.http5.client.example.helper.BaseHttpRequestHelper;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.classic.methods.*;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.*;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.apache.hc.core5.net.URIBuilder;
+
+/**
+ * Utility to handle HTTP requests for user entities. It uses built in types for HTTP processing.
+ */
+@Slf4j
+public class UserSimpleHttpRequestHelper extends BaseHttpRequestHelper {
+
+ /**
+ * Gets user for given user id.
+ *
+ * @param userId user id
+ * @return response if user is found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public String getUser(final long userId) throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ // Create request
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/" + userId).build();
+ final HttpGet httpGetRequest = new HttpGet(uri);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
+ final String responseBody = httpClient.execute(httpHost, httpGetRequest, handler);
+
+ log.info("Got response: {}", responseBody);
+
+ return responseBody;
+ } catch (Exception e) {
+ throw new RequestProcessingException(
+ MessageFormat.format("Failed to get user for ID: {0}", userId), e);
+ }
+ }
+
+ /**
+ * Gets user status for given user id.
+ *
+ * @param userId user id
+ * @return response if user is found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public Integer getUserStatus(final long userId) throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ // Create request
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/" + userId).build();
+ final HttpHead httpHeadRequest = new HttpHead(uri);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpHeadRequest.getMethod(),
+ httpHeadRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ // Implement HttpClientResponseHandler::handleResponse(ClassicHttpResponse response)
+ final HttpClientResponseHandler handler = HttpResponse::getCode;
+ final Integer code = httpClient.execute(httpHost, httpHeadRequest, handler);
+
+ log.info("Got response status code: {}", code);
+
+ return code;
+ } catch (Exception e) {
+ throw new RequestProcessingException(
+ MessageFormat.format("Failed to get user status for ID: {0}", userId), e);
+ }
+ }
+
+ /**
+ * Gets paginated users.
+ *
+ * @param requestParameters request parameters
+ * @return response paginated users
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public String getPaginatedUsers(final Map requestParameters)
+ throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ // Create request
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final List nameValuePairs =
+ requestParameters.entrySet().stream()
+ .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
+ .map(entry -> (NameValuePair) entry)
+ .toList();
+ final HttpGet httpGetRequest =
+ new HttpGet(new URIBuilder("/api/users/").addParameters(nameValuePairs).build());
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
+ final String responseBody = httpClient.execute(httpHost, httpGetRequest, handler);
+
+ log.info("Got response: {}", responseBody);
+ return responseBody;
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to get paginated users.", e);
+ }
+ }
+
+ /**
+ * Creates user for given input.
+ *
+ * @param firstName first name
+ * @param lastName last name
+ * @param email email
+ * @param avatar avatar
+ * @return newly created user
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public String createUser(
+ @NonNull final String firstName,
+ @NonNull final String lastName,
+ @NonNull final String email,
+ @NonNull final String avatar)
+ throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ log.debug(
+ "Create user using input: first name {}, last name {}, email {}, avatar {}",
+ firstName,
+ lastName,
+ email,
+ avatar);
+ // Create request
+ final List formParams = new ArrayList();
+ formParams.add(new BasicNameValuePair("first_name", firstName));
+ formParams.add(new BasicNameValuePair("last_name", lastName));
+ formParams.add(new BasicNameValuePair("email", email));
+ formParams.add(new BasicNameValuePair("avatar", avatar));
+ try (final UrlEncodedFormEntity entity =
+ new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8)) {
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/").build();
+ final HttpPost httpPostRequest = new HttpPost(uri);
+ httpPostRequest.setEntity(entity);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpPostRequest.getMethod(),
+ httpPostRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
+ final String responseBody = httpClient.execute(httpHost, httpPostRequest, handler);
+ log.info("Got response: {}", responseBody);
+
+ return responseBody;
+ }
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to create user.", e);
+ }
+ }
+
+ /**
+ * Updates user for given input.
+ *
+ * @param userId existing user id
+ * @param firstName first name
+ * @param lastName last name
+ * @param email email
+ * @param avatar avatar
+ * @return updated user
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public String updateUser(
+ final long userId,
+ @NonNull final String firstName,
+ @NonNull final String lastName,
+ @NonNull final String email,
+ @NonNull final String avatar)
+ throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ log.debug(
+ "Update user using input: first name {}, last name {}, email {}, avatar {}",
+ firstName,
+ lastName,
+ email,
+ avatar);
+ // Update request
+ final List formParams = new ArrayList();
+ formParams.add(new BasicNameValuePair("first_name", firstName));
+ formParams.add(new BasicNameValuePair("last_name", lastName));
+ formParams.add(new BasicNameValuePair("email", email));
+ formParams.add(new BasicNameValuePair("avatar", avatar));
+
+ try (final UrlEncodedFormEntity entity =
+ new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8)) {
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/" + userId).build();
+ final HttpPut httpPutRequest = new HttpPut(uri);
+ httpPutRequest.setEntity(entity);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpPutRequest.getMethod(),
+ httpPutRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
+ final String responseBody = httpClient.execute(httpHost, httpPutRequest, handler);
+ log.info("Got response: {}", responseBody);
+
+ return responseBody;
+ }
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to update user.", e);
+ }
+ }
+
+ /**
+ * Patches user for given input.
+ *
+ * @param userId existing user id
+ * @param firstName first name
+ * @param lastName last name
+ * @return patched user
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public String patchUser(
+ final long userId, @NonNull final String firstName, @NonNull final String lastName)
+ throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ log.debug("Patch user using input: first name {}, last name {}", firstName, lastName);
+ // Prepare request
+ final List formParams = new ArrayList();
+ formParams.add(new BasicNameValuePair("first_name", firstName));
+ formParams.add(new BasicNameValuePair("last_name", lastName));
+
+ try (final UrlEncodedFormEntity entity =
+ new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8)) {
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/" + userId).build();
+ final HttpPatch httpPatchRequest = new HttpPatch(uri);
+ httpPatchRequest.setEntity(entity);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpPatchRequest.getMethod(),
+ httpPatchRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
+ final String responseBody = httpClient.execute(httpHost, httpPatchRequest, handler);
+ log.info("Got response: {}", responseBody);
+
+ return responseBody;
+ }
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to patch user.", e);
+ }
+ }
+
+ /**
+ * Deletes given user.
+ *
+ * @param userId existing user id
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public void deleteUser(final long userId) throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ log.info("Delete user with ID: {}", userId);
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/" + userId).build();
+ final HttpDelete httpDeleteRequest = new HttpDelete(uri);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpDeleteRequest.getMethod(),
+ httpDeleteRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
+ final String responseBody = httpClient.execute(httpHost, httpDeleteRequest, handler);
+ log.info("Got response: {}", responseBody);
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to update user.", e);
+ }
+ }
+
+ /**
+ * Executes HTTP OPTIONS method on the server.
+ *
+ * @return headers
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public Map executeOptions() throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ final HttpHost httpHost = HttpHost.create("https://reqres.in");
+ final URI uri = new URIBuilder("/api/users/").build();
+ final HttpOptions httpOptionsRequest = new HttpOptions(uri);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpOptionsRequest.getMethod(),
+ httpOptionsRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ // Implement HttpClientResponseHandler::handleResponse(ClassicHttpResponse response)
+ final HttpClientResponseHandler> handler =
+ response ->
+ StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ response.headerIterator(), Spliterator.ORDERED),
+ false)
+ .collect(Collectors.toMap(Header::getName, Header::getValue));
+ final Map headers = httpClient.execute(httpHost, httpOptionsRequest, handler);
+ log.info("Got headers: {}", headers);
+ return headers;
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to execute the request.", e);
+ }
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/helper/UserTypeHttpRequestHelper.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/helper/UserTypeHttpRequestHelper.java
new file mode 100644
index 000000000..c117f9aa1
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/helper/UserTypeHttpRequestHelper.java
@@ -0,0 +1,154 @@
+package io.refactoring.http5.client.example.classic.helper;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import io.refactoring.http5.client.example.classic.handler.DataObjectResponseHandler;
+import io.refactoring.http5.client.example.classic.util.UserRequestProcessingUtils;
+import io.refactoring.http5.client.example.helper.BaseHttpRequestHelper;
+import io.refactoring.http5.client.example.model.User;
+import io.refactoring.http5.client.example.model.UserPage;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.Map;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.classic.methods.HttpPut;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+
+/**
+ * Utility to handle HTTP requests for {@linkplain User} entities. It uses the user-defined types
+ * for HTTP processing.
+ */
+@Slf4j
+public class UserTypeHttpRequestHelper extends BaseHttpRequestHelper {
+
+ private final UserRequestProcessingUtils userRequestProcessingUtils =
+ new UserRequestProcessingUtils();
+
+ /**
+ * Gets user for given user id.
+ *
+ * @param userId user id
+ * @return user if found
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public User getUser(final long userId) throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ // Create request
+ final HttpHost httpHost = userRequestProcessingUtils.getApiHost();
+ final URI uri = userRequestProcessingUtils.prepareUsersApiUri(userId);
+ final HttpGet httpGetRequest = new HttpGet(uri);
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final HttpClientResponseHandler handler = new DataObjectResponseHandler<>(User.class);
+ final User existingUser = httpClient.execute(httpHost, httpGetRequest, handler);
+ log.info("Got response: {}", jsonUtils.toJson(existingUser));
+ return existingUser;
+ } catch (Exception e) {
+ throw new RequestProcessingException(
+ MessageFormat.format("Failed to get user for ID: {0}", userId), e);
+ }
+ }
+
+ /**
+ * Gets paginated users.
+ *
+ * @param requestParameters request parameters
+ * @return response
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public UserPage getPaginatedUsers(final Map requestParameters)
+ throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ // Create request
+ final HttpHost httpHost = userRequestProcessingUtils.getApiHost();
+ final HttpGet httpGetRequest =
+ new HttpGet(userRequestProcessingUtils.prepareUsersApiUri(requestParameters));
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpGetRequest.getMethod(),
+ httpGetRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final HttpClientResponseHandler handler =
+ new DataObjectResponseHandler<>(UserPage.class);
+ final UserPage responseBody = httpClient.execute(httpHost, httpGetRequest, handler);
+
+ log.info("Got response: {}", jsonUtils.toJson(responseBody));
+ return responseBody;
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to get paginated users.", e);
+ }
+ }
+
+ /**
+ * Creates user for given input.
+ *
+ * @param input user creation input
+ * @return newly created user
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public User createUser(@NonNull final User input) throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ log.debug("Create user using input: {}", jsonUtils.toJson(input));
+ // Create request
+ final HttpHost httpHost = userRequestProcessingUtils.getApiHost();
+ final HttpPost httpPostRequest =
+ new HttpPost(userRequestProcessingUtils.prepareUsersApiUri());
+ httpPostRequest.setEntity(userRequestProcessingUtils.toJsonStringEntity(input));
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpPostRequest.getMethod(),
+ httpPostRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final DataObjectResponseHandler handler = new DataObjectResponseHandler<>(User.class);
+ final User createdUser = httpClient.execute(httpHost, httpPostRequest, handler);
+ log.info("Got response: {}", jsonUtils.toJson(createdUser));
+ return createdUser;
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to create user.", e);
+ }
+ }
+
+ /**
+ * Updates user for given input.
+ *
+ * @param input user update input
+ * @return updated user
+ * @throws RequestProcessingException if failed to execute request
+ */
+ public User updateUser(@NonNull final User input) throws RequestProcessingException {
+ try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ log.debug("Update user using input: {}", jsonUtils.toJson(input));
+ // Update request
+ final HttpHost httpHost = userRequestProcessingUtils.getApiHost();
+ final HttpPut httpPutRequest = new HttpPut(userRequestProcessingUtils.prepareUsersApiUri());
+ httpPutRequest.setEntity(userRequestProcessingUtils.toJsonStringEntity(input));
+ log.debug(
+ "Executing {} request: {} on host {}",
+ httpPutRequest.getMethod(),
+ httpPutRequest.getUri(),
+ httpHost);
+
+ // Create a response handler
+ final DataObjectResponseHandler handler = new DataObjectResponseHandler<>(User.class);
+ final User updatedUser = httpClient.execute(httpHost, httpPutRequest, handler);
+ log.info("Got response: {}", jsonUtils.toJson(updatedUser));
+ return updatedUser;
+ } catch (Exception e) {
+ throw new RequestProcessingException("Failed to update user.", e);
+ }
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/util/RequestProcessingUtils.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/util/RequestProcessingUtils.java
new file mode 100644
index 000000000..12db3a09f
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/util/RequestProcessingUtils.java
@@ -0,0 +1,38 @@
+package io.refactoring.http5.client.example.classic.util;
+
+import java.net.URISyntaxException;
+
+import io.refactoring.http5.client.example.config.ConfigurationUtils;
+import io.refactoring.http5.client.example.util.JsonUtils;
+import lombok.NonNull;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+/** Utility for HTTP client interactions. */
+public abstract class RequestProcessingUtils {
+
+ protected final JsonUtils jsonUtils = new JsonUtils();
+
+ protected final ConfigurationUtils configurationUtils = new ConfigurationUtils();
+
+ /**
+ * Converts a source object into a string entity.
+ *
+ * @param source source object
+ * @return string entity
+ */
+ public StringEntity toJsonStringEntity(@NonNull final Object source) {
+ return new StringEntity(jsonUtils.toJson(source), ContentType.APPLICATION_JSON);
+ }
+
+ /**
+ * Gets API host.
+ *
+ * @return API host
+ * @throws URISyntaxException if failed to get API host
+ */
+ public HttpHost getApiHost() throws URISyntaxException {
+ return HttpHost.create(configurationUtils.getString("app.prop.api-host"));
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/util/UserRequestProcessingUtils.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/util/UserRequestProcessingUtils.java
new file mode 100644
index 000000000..fa421c850
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/classic/util/UserRequestProcessingUtils.java
@@ -0,0 +1,70 @@
+package io.refactoring.http5.client.example.classic.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+import lombok.NonNull;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.apache.hc.core5.net.URIBuilder;
+
+/** Utility for HTTP client interactions for user's domain. */
+public class UserRequestProcessingUtils extends RequestProcessingUtils {
+
+ /**
+ * Gets users API URI.
+ *
+ * @return users API URI
+ */
+ protected URI getUsersApiUri() {
+ return URI.create(configurationUtils.getString("app.prop.uri-users-api"));
+ }
+
+ /**
+ * Gets users API page size.
+ *
+ * @return users API page size
+ */
+ public long getUsersApiPageSize() {
+ return configurationUtils.getLong("app.prop.uri-users-api-page-size");
+ }
+
+ /**
+ * Prepares URI for API to operate on users.
+ *
+ * @return users API URI, for example, /api/users
+ * @throws URISyntaxException if failed to prepare users API URI
+ */
+ public URI prepareUsersApiUri() throws URISyntaxException {
+ return new URIBuilder(getUsersApiUri()).build();
+ }
+
+ /**
+ * Prepares URI for API to get user by ID.
+ *
+ * @param userId ID of user to be fetched
+ * @return get user by id API URI, for example, /api/users/123
+ * @throws URISyntaxException if failed to prepare API URI
+ */
+ public URI prepareUsersApiUri(final long userId) throws URISyntaxException {
+ return new URIBuilder(getUsersApiUri() + "/" + userId).build();
+ }
+
+ /**
+ * Prepares URI for API to fetch users using request parameters.
+ *
+ * @param parameters request parameters
+ * @return users API URI, for example, /api/users?page=1
+ * @throws URISyntaxException if failed to prepare API URI
+ */
+ public URI prepareUsersApiUri(@NonNull final Map parameters)
+ throws URISyntaxException {
+ final List nameValuePairs =
+ parameters.entrySet().stream()
+ .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
+ .map(entry -> (NameValuePair) entry)
+ .toList();
+ return new URIBuilder(getUsersApiUri()).addParameters(nameValuePairs).build();
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/ConfigurationUtils.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/ConfigurationUtils.java
new file mode 100644
index 000000000..f10628e6c
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/ConfigurationUtils.java
@@ -0,0 +1,56 @@
+package io.refactoring.http5.client.example.config;
+
+import lombok.NonNull;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.FileBasedConfiguration;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
+import org.apache.commons.configuration2.builder.fluent.Parameters;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.ClasspathLocationStrategy;
+
+/** Utility to load configurations. */
+public class ConfigurationUtils {
+ private Configuration config;
+
+ @NonNull
+ private Configuration getConfiguration() {
+ if (config == null) {
+ try {
+ final FileBasedConfigurationBuilder builder =
+ new FileBasedConfigurationBuilder(PropertiesConfiguration.class)
+ .configure(
+ new Parameters()
+ .properties()
+ .setLocationStrategy(new ClasspathLocationStrategy())
+ .setFileName("application-test.properties"));
+ config = builder.getConfiguration();
+ } catch (ConfigurationException e) {
+ throw new RuntimeException("Failed to load properties into configuration.", e);
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Gets string property.
+ *
+ * @param key property key
+ * @return property value
+ * @throws NullPointerException if {@code key} is null
+ */
+ public String getString(@NonNull final String key) {
+ return getConfiguration().getString(key);
+ }
+
+ /**
+ * Gets long property.
+ *
+ * @param key property key
+ * @return property value
+ * @throws NullPointerException if {@code key} is null
+ */
+ public long getLong(@NonNull final String key) {
+ return getConfiguration().getLong(key);
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/helper/HttpConfigurationHelper.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/helper/HttpConfigurationHelper.java
new file mode 100644
index 000000000..50e4eac08
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/helper/HttpConfigurationHelper.java
@@ -0,0 +1,162 @@
+package io.refactoring.http5.client.example.config.helper;
+
+import io.refactoring.http5.client.example.config.interceptor.CustomHttpExecutionInterceptor;
+import io.refactoring.http5.client.example.config.interceptor.CustomHttpRequestInterceptor;
+import io.refactoring.http5.client.example.config.interceptor.CustomHttpResponseInterceptor;
+import io.refactoring.http5.client.example.helper.BaseHttpRequestHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.CachingHttpClientBuilder;
+import org.apache.hc.client5.http.impl.cache.CachingHttpClients;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.core5.http.*;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+
+/** Utility to handle HTTP client configurations. */
+@Slf4j
+public class HttpConfigurationHelper extends BaseHttpRequestHelper {
+
+ /**
+ * Populates request config.
+ *
+ * @param httpClientBuilder the http client builder
+ * @param requestTimeoutMillis the request timeout millis
+ * @param responseTimeoutMillis the response timeout millis
+ * @param connectionKeepAliveMillis the connection keep alive millis
+ * @return http client builder
+ */
+ public HttpClientBuilder populateRequestConfig(
+ HttpClientBuilder httpClientBuilder,
+ final long requestTimeoutMillis,
+ final long responseTimeoutMillis,
+ final long connectionKeepAliveMillis) {
+ final Timeout requestTimeout = Timeout.ofMilliseconds(requestTimeoutMillis);
+ final Timeout responseTimeout = Timeout.ofMilliseconds(responseTimeoutMillis);
+ final TimeValue connectionKeepAlive = TimeValue.ofMilliseconds(connectionKeepAliveMillis);
+
+ final RequestConfig requestConfig =
+ RequestConfig.custom()
+ .setConnectionRequestTimeout(requestTimeout)
+ .setResponseTimeout(responseTimeout)
+ .setConnectionKeepAlive(connectionKeepAlive)
+ .build();
+ return httpClientBuilder.setDefaultRequestConfig(requestConfig);
+ }
+
+ /**
+ * Populates caching config.
+ *
+ * @param maxCacheEntries the max cache entries
+ * @param maxObjectSize the max object size
+ * @return the caching http client builder
+ */
+ public CachingHttpClientBuilder populateCachingConfig(
+ final int maxCacheEntries, final int maxObjectSize) {
+ final CacheConfig cacheConfig =
+ CacheConfig.custom()
+ .setMaxCacheEntries(maxCacheEntries)
+ .setMaxObjectSize(maxObjectSize)
+ .build();
+ return CachingHttpClients.custom().setCacheConfig(cacheConfig);
+ }
+
+ /**
+ * Gets pooled closeable http client.
+ *
+ * @param host the host
+ * @return the pooled closeable http client
+ */
+ public CloseableHttpClient getPooledCloseableHttpClient(final String host) {
+ // Increase max total connection to 200
+ // Increase default max per route connection per route to 20
+ return getPooledCloseableHttpClient(host, 80, 200, 20, 1000, 1000, 1000);
+ }
+
+ /**
+ * Gets pooled closeable http client.
+ *
+ * @param host the host
+ * @param port the port
+ * @param maxTotalConnections the max total connections
+ * @param defaultMaxPerRoute the default max per route
+ * @param requestTimeoutMillis the request timeout millis
+ * @param responseTimeoutMillis the response timeout millis
+ * @param connectionKeepAliveMillis the connection keep alive millis
+ * @return the pooled closeable http client
+ */
+ public CloseableHttpClient getPooledCloseableHttpClient(
+ final String host,
+ int port,
+ int maxTotalConnections,
+ int defaultMaxPerRoute,
+ long requestTimeoutMillis,
+ long responseTimeoutMillis,
+ long connectionKeepAliveMillis) {
+ final PoolingHttpClientConnectionManager connectionManager =
+ new PoolingHttpClientConnectionManager();
+ connectionManager.setMaxTotal(maxTotalConnections);
+ connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
+
+ final HttpHost httpHost = new HttpHost(host, port);
+ connectionManager.setMaxPerRoute(new HttpRoute(httpHost), 50);
+ HttpClientBuilder httpClientBuilder = HttpClients.custom();
+ httpClientBuilder =
+ populateRequestConfig(
+ httpClientBuilder,
+ requestTimeoutMillis,
+ responseTimeoutMillis,
+ connectionKeepAliveMillis);
+ return httpClientBuilder.setConnectionManager(connectionManager).build();
+ }
+
+ /**
+ * Gets cached closeable http client.
+ *
+ * @param maxCacheEntries the max cache entries
+ * @param maxObjectSize the max object size
+ * @return the cached closeable http client
+ */
+ public CloseableHttpClient getCachedCloseableHttpClient(
+ final int maxCacheEntries, final int maxObjectSize) {
+ return populateCachingConfig(maxCacheEntries, maxObjectSize).build();
+ }
+
+ /**
+ * Gets request intercepting closeable http client.
+ *
+ * @return the request intercepting closeable http client
+ */
+ public CloseableHttpClient getRequestInterceptingCloseableHttpClient() {
+ return HttpClients.custom()
+ .addRequestInterceptorFirst(new CustomHttpRequestInterceptor())
+ .build();
+ }
+
+ /**
+ * Gets response intercepting closeable http client.
+ *
+ * @return the response intercepting closeable http client
+ */
+ public CloseableHttpClient getResponseInterceptingCloseableHttpClient() {
+ return HttpClients.custom()
+ .addResponseInterceptorFirst(new CustomHttpResponseInterceptor())
+ .build();
+ }
+
+ /**
+ * Gets execution intercepting closeable http client.
+ *
+ * @return the exec intercepting closeable http client
+ */
+ public CloseableHttpClient getExecInterceptingCloseableHttpClient() {
+ return HttpClients.custom()
+ .addExecInterceptorFirst("customExecInterceptor", new CustomHttpExecutionInterceptor())
+ .build();
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpExecutionInterceptor.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpExecutionInterceptor.java
new file mode 100644
index 000000000..bed220acc
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpExecutionInterceptor.java
@@ -0,0 +1,30 @@
+package io.refactoring.http5.client.example.config.interceptor;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import java.io.IOException;
+import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.core5.http.*;
+
+/** The Custom http request interceptor. */
+@Slf4j
+public class CustomHttpExecutionInterceptor implements ExecChainHandler {
+ @Override
+ public ClassicHttpResponse execute(ClassicHttpRequest classicHttpRequest, ExecChain.Scope scope, ExecChain execChain) throws IOException, HttpException {
+ try {
+ classicHttpRequest.setHeader("x-request-id", UUID.randomUUID().toString());
+ classicHttpRequest.setHeader("x-api-key", "secret-key");
+
+ final ClassicHttpResponse response = execChain.proceed(classicHttpRequest, scope);
+ log.debug("Got {} response from server.", response.getCode());
+
+ return response;
+ } catch (IOException | HttpException ex) {
+ String msg = "Failed to execute request.";
+ log.error(msg, ex);
+ throw new RequestProcessingException(msg, ex);
+ }
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpRequestInterceptor.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpRequestInterceptor.java
new file mode 100644
index 000000000..5fd01aabc
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpRequestInterceptor.java
@@ -0,0 +1,19 @@
+package io.refactoring.http5.client.example.config.interceptor;
+
+import java.io.IOException;
+import java.util.UUID;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/** The Custom http request interceptor. */
+public class CustomHttpRequestInterceptor implements HttpRequestInterceptor {
+ @Override
+ public void process(HttpRequest request, EntityDetails entity, HttpContext context)
+ throws HttpException, IOException {
+ request.setHeader("x-request-id", UUID.randomUUID().toString());
+ request.setHeader("x-api-key", "secret-key");
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpResponseInterceptor.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpResponseInterceptor.java
new file mode 100644
index 000000000..1bf6aa5e1
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/CustomHttpResponseInterceptor.java
@@ -0,0 +1,16 @@
+package io.refactoring.http5.client.example.config.interceptor;
+
+import java.io.IOException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.core5.http.*;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/** The Custom http response interceptor. */
+@Slf4j
+public class CustomHttpResponseInterceptor implements HttpResponseInterceptor {
+ @Override
+ public void process(HttpResponse response, EntityDetails entity, HttpContext context)
+ throws HttpException, IOException {
+ log.debug("Got {} response from server.", response.getCode());
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/UserResponseAsyncExecChainHandler.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/UserResponseAsyncExecChainHandler.java
new file mode 100644
index 000000000..210c70adf
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/config/interceptor/UserResponseAsyncExecChainHandler.java
@@ -0,0 +1,74 @@
+package io.refactoring.http5.client.example.config.interceptor;
+
+import io.refactoring.http5.client.example.RequestProcessingException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.async.AsyncExecChainHandler;
+import org.apache.hc.core5.http.*;
+import org.apache.hc.core5.http.impl.BasicEntityDetails;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+
+/** The Custom response async chain handler. */
+@Slf4j
+public class UserResponseAsyncExecChainHandler implements AsyncExecChainHandler {
+ @Override
+ public void execute(
+ HttpRequest httpRequest,
+ AsyncEntityProducer asyncEntityProducer,
+ AsyncExecChain.Scope scope,
+ AsyncExecChain asyncExecChain,
+ AsyncExecCallback asyncExecCallback)
+ throws HttpException, IOException {
+ try {
+ boolean requestHandled = false;
+ if (httpRequest.containsHeader("x-base-number")
+ && httpRequest.containsHeader("x-req-exec-number")) {
+ final String path = httpRequest.getPath();
+ if (StringUtils.startsWith(path, "/api/users/")) {
+ requestHandled = handleUserRequest(httpRequest, asyncExecCallback);
+ }
+ }
+ if (!requestHandled) {
+ asyncExecChain.proceed(httpRequest, asyncEntityProducer, scope, asyncExecCallback);
+ }
+ } catch (IOException | HttpException ex) {
+ String msg = "Failed to execute request.";
+ log.error(msg, ex);
+ throw new RequestProcessingException(msg, ex);
+ }
+ }
+
+ private boolean handleUserRequest(HttpRequest httpRequest, AsyncExecCallback asyncExecCallback)
+ throws HttpException, IOException {
+ boolean requestHandled = false;
+ final Header baseNumberHeader = httpRequest.getFirstHeader("x-base-number");
+ final String baseNumberStr = baseNumberHeader.getValue();
+ int baseNumber = Integer.parseInt(baseNumberStr);
+
+ final Header reqExecNumberHeader = httpRequest.getFirstHeader("x-req-exec-number");
+ final String reqExecNumberStr = reqExecNumberHeader.getValue();
+ int reqExecNumber = Integer.parseInt(reqExecNumberStr);
+
+ // check if user id is multiple of base value
+ if (reqExecNumber % baseNumber == 0) {
+ final String reasonPhrase = "Multiple of " + baseNumber;
+ final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK, reasonPhrase);
+ final ByteBuffer content = ByteBuffer.wrap(reasonPhrase.getBytes(StandardCharsets.US_ASCII));
+ final BasicEntityDetails entityDetails =
+ new BasicEntityDetails(content.remaining(), ContentType.TEXT_PLAIN);
+ final AsyncDataConsumer asyncDataConsumer =
+ asyncExecCallback.handleResponse(response, entityDetails);
+ asyncDataConsumer.consume(content);
+ asyncDataConsumer.streamEnd(null);
+ requestHandled = true;
+ }
+ return requestHandled;
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/helper/BaseHttpRequestHelper.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/helper/BaseHttpRequestHelper.java
new file mode 100644
index 000000000..409fda6d4
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/helper/BaseHttpRequestHelper.java
@@ -0,0 +1,11 @@
+package io.refactoring.http5.client.example.helper;
+
+import io.refactoring.http5.client.example.util.JsonUtils;
+import lombok.extern.slf4j.Slf4j;
+
+/** Base HTTP request handler. */
+@Slf4j
+public abstract class BaseHttpRequestHelper {
+ protected final JsonUtils jsonUtils = new JsonUtils();
+
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/BasePage.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/BasePage.java
new file mode 100644
index 000000000..cc8795886
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/BasePage.java
@@ -0,0 +1,53 @@
+package io.refactoring.http5.client.example.model;
+
+/**
+ * Represents ansible abstract page.
+ *
+ * @param type of paginated data item
+ */
+public abstract class BasePage extends PaginatedEntities {
+
+ /** The page number. */
+ private Long page;
+
+ /** The number of items per page. */
+ private Long perPage;
+
+ /** The number of total items. */
+ private Long total;
+
+ /** The number of pages. */
+ private Long totalPages;
+
+ public Long getPage() {
+ return page;
+ }
+
+ public void setPage(Long page) {
+ this.page = page;
+ }
+
+ public Long getPerPage() {
+ return perPage;
+ }
+
+ public void setPerPage(Long perPage) {
+ this.perPage = perPage;
+ }
+
+ public Long getTotal() {
+ return total;
+ }
+
+ public void setTotal(Long total) {
+ this.total = total;
+ }
+
+ public Long getTotalPages() {
+ return totalPages;
+ }
+
+ public void setTotalPages(Long totalPages) {
+ this.totalPages = totalPages;
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/DataObject.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/DataObject.java
new file mode 100644
index 000000000..1e79d9ceb
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/DataObject.java
@@ -0,0 +1,18 @@
+package io.refactoring.http5.client.example.model;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/** Represents data object. */
+@JsonAutoDetect(
+ fieldVisibility = Visibility.NONE,
+ getterVisibility = Visibility.PUBLIC_ONLY,
+ setterVisibility = Visibility.ANY,
+ isGetterVisibility = Visibility.PUBLIC_ONLY)
+@JsonInclude(value = Include.NON_EMPTY, content = Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public abstract class DataObject {
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/DataWithId.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/DataWithId.java
new file mode 100644
index 000000000..fd3ebac39
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/DataWithId.java
@@ -0,0 +1,105 @@
+package io.refactoring.http5.client.example.model;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import java.util.Date;
+
+/** Represents data objects with identifiers. */
+public abstract class DataWithId extends DataObject {
+ private long id;
+
+ /* Date of creation, for example, 2024-02-18T13:47:25.842Z
+ */
+ @JsonFormat(
+ shape = JsonFormat.Shape.STRING,
+ pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ timezone = "GMT")
+ private Date createdAt;
+
+ /* Date of update, for example, 2024-02-18T13:47:25.842Z
+ */
+ @JsonFormat(
+ shape = JsonFormat.Shape.STRING,
+ pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ timezone = "GMT")
+ private Date updatedAt;
+
+ /* Date of deletion, for example, 2024-02-18T13:47:25.842Z
+ */
+ @JsonFormat(
+ shape = JsonFormat.Shape.STRING,
+ pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ timezone = "GMT")
+ private Date deletedAt;
+
+ /**
+ * Gets id.
+ *
+ * @return the id
+ */
+public long getId() {
+ return id;
+ }
+
+ /**
+ * Sets id.
+ *
+ * @param id the id
+ */
+public void setId(long id) {
+ this.id = id;
+ }
+
+ /**
+ * Gets created at.
+ *
+ * @return the created at
+ */
+public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * Sets created at.
+ *
+ * @param createdAt the created at
+ */
+public void setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ /**
+ * Gets updated at.
+ *
+ * @return the updated at
+ */
+public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ /**
+ * Sets updated at.
+ *
+ * @param updatedAt the updated at
+ */
+public void setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ /**
+ * Gets deleted at.
+ *
+ * @return the deleted at
+ */
+public Date getDeletedAt() {
+ return deletedAt;
+ }
+
+ /**
+ * Sets deleted at.
+ *
+ * @param deletedAt the deleted at
+ */
+public void setDeletedAt(Date deletedAt) {
+ this.deletedAt = deletedAt;
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/PaginatedEntities.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/PaginatedEntities.java
new file mode 100644
index 000000000..27fb4ec6e
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/PaginatedEntities.java
@@ -0,0 +1,36 @@
+package io.refactoring.http5.client.example.model;
+
+import java.util.ArrayList;
+
+/**
+ * Represents paginated entities.
+ *
+ * @param type of paginated data item
+ */
+public class PaginatedEntities extends DataObject {
+ private ArrayList data;
+
+ public ArrayList getData() {
+ return data(true);
+ }
+
+ public void setData(ArrayList data) {
+ this.data = data;
+ }
+
+ protected ArrayList data(boolean autoCreate) {
+ if (data == null && autoCreate) {
+ data = new ArrayList<>();
+ }
+ return data;
+ }
+
+ /**
+ * Adds given item to paginated data list.
+ *
+ * @param item source item
+ */
+ public void addContent(T item) {
+ data(true).add(item);
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/ResponseDataObject.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/ResponseDataObject.java
new file mode 100644
index 000000000..c95e6b450
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/ResponseDataObject.java
@@ -0,0 +1,18 @@
+package io.refactoring.http5.client.example.model;
+
+/**
+ * Represents response containing given data.
+ *
+ * @param data type
+ */
+public class ResponseDataObject extends DataObject {
+ private T data;
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/User.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/User.java
new file mode 100644
index 000000000..e4a991611
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/User.java
@@ -0,0 +1,48 @@
+package io.refactoring.http5.client.example.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/** Represents users. */
+public class User extends DataWithId {
+ private String email;
+
+ @JsonProperty("first_name")
+ private String firstName;
+
+ @JsonProperty("last_name")
+ private String lastName;
+
+ private String avatar;
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/UserPage.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/UserPage.java
new file mode 100644
index 000000000..5d020a8fe
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/model/UserPage.java
@@ -0,0 +1,4 @@
+package io.refactoring.http5.client.example.model;
+
+/** Represents paginated users listing. */
+public class UserPage extends BasePage {}
diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/util/JsonUtils.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/util/JsonUtils.java
new file mode 100644
index 000000000..c5406e4a0
--- /dev/null
+++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/util/JsonUtils.java
@@ -0,0 +1,78 @@
+package io.refactoring.http5.client.example.util;
+
+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 lombok.NonNull;
+import org.json.JSONObject;
+
+/** Utility to handle JSON. */
+public class JsonUtils {
+ private ObjectMapper objectMapper;
+
+ private ObjectMapper getObjectMapper() {
+ if (objectMapper == null) {
+ objectMapper = new ObjectMapper();
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+ return objectMapper;
+ }
+
+ /**
+ * Converts a source object into JSON string.
+ *
+ * @param source source object
+ * @return JSON representation of the object, {@code null} if {@code source} is {@code null}
+ * @throws RuntimeException if fails to convert the {@code source} to JSON
+ */
+ public String toJson(final Object source) {
+ if (source == null) {
+ return null;
+ }
+ try {
+ return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(source);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Converts a source string into an object of target class type.
+ *
+ * @param jsonString source JSON
+ * @param targetClassType class type of object
+ * @return object of a {@code targetClassType}
+ * @param type of target object
+ * @throws JsonProcessingException if it fails to process the JSON
+ */
+ public T fromJson(@NonNull final String jsonString, @NonNull final Class targetClassType)
+ throws JsonProcessingException {
+ return getObjectMapper().readValue(jsonString, targetClassType);
+ }
+
+ /**
+ * Converts a source string into an object of target type.
+ *
+ * @param jsonString source JSON
+ * @param targetTpeReference type of object
+ * @return object of a {@code targetTpeReference}
+ * @param type of target object
+ * @throws JsonProcessingException if it fails to process the JSON
+ */
+ public T fromJson(
+ @NonNull final String jsonString, @NonNull final TypeReference targetTpeReference)
+ throws JsonProcessingException {
+ return getObjectMapper().readValue(jsonString, targetTpeReference);
+ }
+
+ /**
+ * Converts source JSON into pretty formatter JSON.
+ *
+ * @param source source JSON
+ * @return formatted JSON
+ */
+ public String makePretty(final String source) {
+ return new JSONObject(source).toString(2);
+ }
+}
diff --git a/apache-http-client/src/main/resources/logback.xml b/apache-http-client/src/main/resources/logback.xml
new file mode 100644
index 000000000..eb0dfc545
--- /dev/null
+++ b/apache-http-client/src/main/resources/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+ %date [%level] %logger - %msg %n
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/BaseExampleTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/BaseExampleTests.java
new file mode 100644
index 000000000..bfb760d41
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/BaseExampleTests.java
@@ -0,0 +1,4 @@
+package io.refactoring.http5.client.example;
+
+/** Base class for all examples. */
+public abstract class BaseExampleTests {}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/BaseAsyncExampleTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/BaseAsyncExampleTests.java
new file mode 100644
index 000000000..22346d212
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/BaseAsyncExampleTests.java
@@ -0,0 +1,8 @@
+package io.refactoring.http5.client.example.async.helper;
+
+import io.refactoring.http5.client.example.BaseExampleTests;
+import io.refactoring.http5.client.example.util.JsonUtils;
+
+abstract class BaseAsyncExampleTests extends BaseExampleTests {
+ protected final JsonUtils jsonUtils = new JsonUtils();
+}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelperTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelperTests.java
new file mode 100644
index 000000000..c6f832cd5
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelperTests.java
@@ -0,0 +1,189 @@
+package io.refactoring.http5.client.example.async.helper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.refactoring.http5.client.example.model.User;
+import java.util.List;
+import java.util.Map;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient;
+import org.assertj.core.api.Condition;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** User async http request helper tests. */
+class UserAsyncHttpRequestHelperTests extends BaseAsyncExampleTests {
+
+ private final UserAsyncHttpRequestHelper userHttpRequestHelper = new UserAsyncHttpRequestHelper();
+
+ private final Condition getUserErrorCheck =
+ new Condition("Check failure response.") {
+ @Override
+ public boolean matches(String value) {
+ // value should not be null
+ // value should not be failure message
+ return value != null
+ && (!value.startsWith("Failed to get user")
+ || value.equals("Server does not support HTTP/2 multiplexing."));
+ }
+ };
+
+ /** Tests get user. */
+ @Test
+ void getUserWithCallback() {
+ try {
+ userHttpRequestHelper.startHttpAsyncClient();
+
+ // Send 10 requests in parallel
+ // call the delayed endpoint
+ final List userIdList =
+ List.of("/httpbin/ip", "/httpbin/user-agent", "/httpbin/headers");
+ final Map responseBodyMap =
+ userHttpRequestHelper.getUserWithCallback(userIdList, 3);
+
+ // verify
+ assertThat(responseBodyMap)
+ .hasSameSizeAs(userIdList)
+ .doesNotContainKey(null)
+ .doesNotContainValue(null)
+ .hasValueSatisfying(getUserErrorCheck);
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ } finally {
+ userHttpRequestHelper.stopHttpAsyncClient();
+ }
+ }
+
+ /** Tests get user with stream. */
+ @Test
+ void getUserWithStream() {
+ try {
+ userHttpRequestHelper.startHttpAsyncClient();
+
+ // Send 10 requests in parallel
+ // call the delayed endpoint
+ final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L);
+ final Map responseBodyMap =
+ userHttpRequestHelper.getUserWithStreams(userIdList, 3);
+
+ // verify
+ assertThat(responseBodyMap)
+ .hasSameSizeAs(userIdList)
+ .doesNotContainKey(null)
+ .doesNotContainValue(null)
+ .hasValueSatisfying(getUserErrorCheck);
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ } finally {
+ userHttpRequestHelper.stopHttpAsyncClient();
+ }
+ }
+
+ /** Tests get user with pipelining. */
+ @Test
+ void getUserWithPipelining() {
+ MinimalHttpAsyncClient minimalHttpAsyncClient = null;
+ try {
+ minimalHttpAsyncClient = userHttpRequestHelper.startMinimalHttp1AsyncClient();
+
+ // Send 10 requests in parallel
+ // call the delayed endpoint
+ final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L);
+ final Map responseBodyMap =
+ userHttpRequestHelper.getUserWithPipelining(
+ minimalHttpAsyncClient, userIdList, 3, "https", "reqres.in");
+
+ // verify
+ assertThat(responseBodyMap)
+ .hasSameSizeAs(userIdList)
+ .doesNotContainKey(null)
+ .doesNotContainValue(null)
+ .hasValueSatisfying(getUserErrorCheck);
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ } finally {
+ userHttpRequestHelper.stopMinimalHttpAsyncClient(minimalHttpAsyncClient);
+ }
+ }
+
+ /** Tests get user with multiplexing. */
+ @Test
+ void getUserWithMultiplexing() {
+ MinimalHttpAsyncClient minimalHttpAsyncClient = null;
+ try {
+ minimalHttpAsyncClient = userHttpRequestHelper.startMinimalHttp2AsyncClient();
+
+ // Send 10 requests in parallel
+ // call the delayed endpoint
+ final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L);
+ final Map responseBodyMap =
+ userHttpRequestHelper.getUserWithMultiplexing(
+ minimalHttpAsyncClient, userIdList, 3, "https", "reqres.in");
+
+ // verify
+ assertThat(responseBodyMap)
+ .hasSameSizeAs(userIdList)
+ .doesNotContainKey(null)
+ .doesNotContainValue(null)
+ .hasValueSatisfying(getUserErrorCheck);
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ } finally {
+ userHttpRequestHelper.stopMinimalHttpAsyncClient(minimalHttpAsyncClient);
+ }
+ }
+
+ /** Tests get user with async client with interceptor. */
+ @Test
+ void getUserWithInterceptors() {
+ try (final CloseableHttpAsyncClient closeableHttpAsyncClient =
+ userHttpRequestHelper.startHttpAsyncInterceptingClient()) {
+
+ final int baseNumber = 3;
+ final int requestExecCount = 5;
+ final Map responseBodyMap =
+ userHttpRequestHelper.executeRequestsWithInterceptors(
+ closeableHttpAsyncClient, 1L, requestExecCount, baseNumber);
+
+ // verify
+ assertThat(responseBodyMap)
+ .hasSize(requestExecCount)
+ .doesNotContainKey(null)
+ .doesNotContainValue(null)
+ .hasValueSatisfying(getUserErrorCheck);
+
+ final String expectedResponse = "Multiple of " + baseNumber;
+ for (Integer i : responseBodyMap.keySet()) {
+ if (i % baseNumber == 0) {
+ assertThat(responseBodyMap).containsEntry(i, expectedResponse);
+ }
+ }
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ @Test
+ void createUserWithReactiveProcessing() {
+ MinimalHttpAsyncClient minimalHttpAsyncClient = null;
+ try {
+ minimalHttpAsyncClient = userHttpRequestHelper.startMinimalHttp1AsyncClient();
+
+ final User responseBody =
+ userHttpRequestHelper.createUserWithReactiveProcessing(
+ minimalHttpAsyncClient, "RxMan", "Manager", "https", "reqres.in");
+
+ // verify
+ assertThat(responseBody).extracting("id", "createdAt").isNotNull();
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ } finally {
+ userHttpRequestHelper.stopMinimalHttpAsyncClient(minimalHttpAsyncClient);
+ }
+ }
+}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/BaseClassicExampleTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/BaseClassicExampleTests.java
new file mode 100644
index 000000000..dd06f917a
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/BaseClassicExampleTests.java
@@ -0,0 +1,8 @@
+package io.refactoring.http5.client.example.classic;
+
+import io.refactoring.http5.client.example.BaseExampleTests;
+import io.refactoring.http5.client.example.util.JsonUtils;
+
+abstract class BaseClassicExampleTests extends BaseExampleTests {
+ protected final JsonUtils jsonUtils = new JsonUtils();
+}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/BasicClientTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/BasicClientTests.java
new file mode 100644
index 000000000..bc5246ab0
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/BasicClientTests.java
@@ -0,0 +1,55 @@
+package io.refactoring.http5.client.example.classic;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+
+/** This example demonstrates how to process HTTP responses using the HTTP client. */
+@Slf4j
+public class BasicClientTests extends BaseClassicExampleTests {
+
+ @Test
+ void executeGetRequest() {
+// CloseableHttpResponse closeableHttpResponse = null;
+// try (final CloseableHttpClient httpclient = HttpClientBuilder.create().build()) {
+// final ClassicHttpRequest httpGet = new HttpGet("https://reqres.in/api/users?page=1");
+// log.debug("Executing request: {}", httpGet.getRequestUri());
+//
+// // Create a response
+// closeableHttpResponse = httpclient.execute(httpGet);
+//
+// // verify
+// final Consumer responseRequirements =
+// response -> {
+// assertThat(response.getEntity()).as("Failed to get response.").isNotNull();
+// assertThat(response.getProtocolVersion())
+// .as("Invalid protocol version.")
+// .isEqualTo(HttpVersion.HTTP_1_1);
+// assertThat(response.getStatusLine().getProtocolVersion())
+// .as("Invalid protocol version in status line.")
+// .isEqualTo(HttpVersion.HTTP_1_1);
+// assertThat(response.getStatusLine().getStatusCode())
+// .as("Invalid HTTP status in status line.")
+// .isEqualTo(HttpStatus.SC_OK);
+// assertThat(response.getStatusLine().getReasonPhrase())
+// .as("Invalid reason phrase in status line.")
+// .isEqualTo("OK");
+// };
+// assertThat(closeableHttpResponse).satisfies(responseRequirements);
+//
+// final HttpEntity respEntity = closeableHttpResponse.getEntity();
+// final String respStr = EntityUtils.toString(respEntity);
+// log.info("Got response: {}", makePretty(respStr));
+// } catch (IOException e) {
+// fail("Failed to execute HTTP request.", e);
+// } finally {
+// if (closeableHttpResponse != null) {
+// try {
+// closeableHttpResponse.close();
+// } catch (IOException e) {
+// fail("Failed to close the HTTP response.", e);
+// }
+// }
+// }
+ }
+}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/UserSimpleHttpRequestHelperTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/UserSimpleHttpRequestHelperTests.java
new file mode 100644
index 000000000..f6ce963a9
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/UserSimpleHttpRequestHelperTests.java
@@ -0,0 +1,158 @@
+package io.refactoring.http5.client.example.classic;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.refactoring.http5.client.example.classic.helper.UserSimpleHttpRequestHelper;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.core5.http.HttpStatus;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** This example demonstrates how to process HTTP responses using a response handler. */
+@Slf4j
+public class UserSimpleHttpRequestHelperTests extends BaseClassicExampleTests {
+
+ private final UserSimpleHttpRequestHelper userHttpRequestHelper =
+ new UserSimpleHttpRequestHelper();
+
+ /** Execute get paginated request. */
+ @Test
+ void executeGetPaginatedRequest() {
+ try {
+ // prepare
+ final Map params = Map.of("page", "1");
+
+ // execute
+ final String responseBody = userHttpRequestHelper.getPaginatedUsers(params);
+
+ // verify
+ assertThat(responseBody).isNotEmpty();
+ log.info("Got response: {}", responseBody);
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute get specific request. */
+ @Test
+ void executeGetSpecificRequest() {
+ try {
+ // prepare
+ final long userId = 2L;
+
+ // execute
+ final String existingUser = userHttpRequestHelper.getUser(userId);
+
+ // verify
+ assertThat(existingUser).isNotEmpty();
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute get specific request. */
+ @Test
+ void executeUserStatus() {
+ try {
+ // prepare
+ final long userId = 2L;
+
+ // execute
+ final Integer userStatus = userHttpRequestHelper.getUserStatus(userId);
+
+ // verify
+ assertThat(userStatus).isEqualTo(HttpStatus.SC_OK);
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute post request. */
+ @Test
+ void executePostRequest() {
+ try {
+ // prepare
+ // execute
+ final String createdUser =
+ userHttpRequestHelper.createUser(
+ "DummyFirst", "DummyLast", "DummyEmail@example.com", "DummyAvatar");
+
+ // verify
+ assertThat(createdUser).isNotEmpty();
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute put request. */
+ @Test
+ void executePutRequest() {
+ try {
+ // prepare
+ final int userId = 2;
+
+ // execute
+ final String updatedUser =
+ userHttpRequestHelper.updateUser(
+ userId,
+ "UpdatedDummyFirst",
+ "UpdatedDummyLast",
+ "UpdatedDummyEmail@example.com",
+ "UpdatedDummyAvatar");
+
+ // verify
+ assertThat(updatedUser).isNotEmpty();
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute put request. */
+ @Test
+ void executePatchRequest() {
+ try {
+ // prepare
+ final int userId = 2;
+
+ // execute
+ final String patchedUser =
+ userHttpRequestHelper.patchUser(userId, "UpdatedDummyFirst", "UpdatedDummyLast");
+
+ // verify
+ assertThat(patchedUser).isNotEmpty();
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute delete request. */
+ @Test
+ void executeDeleteRequest() {
+ try {
+ // prepare
+ final int userId = 2;
+
+ // execute
+ userHttpRequestHelper.deleteUser(userId);
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ /** Execute options request. */
+ @Test
+ void executeOptions() {
+ try {
+ // execute
+ final Map headers = userHttpRequestHelper.executeOptions();
+ assertThat(headers.keySet())
+ .as("Headers do not contain allow header")
+ .containsAnyOf("Allow", "Access-Control-Allow-Methods");
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/UserTypeHttpRequestHelperTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/UserTypeHttpRequestHelperTests.java
new file mode 100644
index 000000000..218aa9881
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/classic/UserTypeHttpRequestHelperTests.java
@@ -0,0 +1,129 @@
+package io.refactoring.http5.client.example.classic;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.refactoring.http5.client.example.model.User;
+import io.refactoring.http5.client.example.model.UserPage;
+import io.refactoring.http5.client.example.classic.helper.UserTypeHttpRequestHelper;
+import java.util.Map;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.assertj.core.api.ThrowingConsumer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** This example demonstrates how to process HTTP responses using a response handler. */
+@Slf4j
+public class UserTypeHttpRequestHelperTests extends BaseClassicExampleTests {
+
+ private final UserTypeHttpRequestHelper userHttpRequestHelper = new UserTypeHttpRequestHelper();
+
+ @Test
+ void executeGetAllRequest() {
+ try {
+ // prepare
+ final Map params = Map.of("page", "1");
+
+ // execute
+ final UserPage paginatedUsers = userHttpRequestHelper.getPaginatedUsers(params);
+
+ // verify
+ assertThat(paginatedUsers).isNotNull();
+ log.info("Got response: {}", jsonUtils.toJson(paginatedUsers));
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ @Test
+ void executeGetUser() {
+ try {
+ // prepare
+ final long userId = 2L;
+
+ // execute
+ final User existingUser = userHttpRequestHelper.getUser(userId);
+
+ // verify
+ final ThrowingConsumer responseRequirements =
+ user -> {
+ assertThat(user).as("Created user cannot be null.").isNotNull();
+ assertThat(user.getId()).as("ID should be positive number.").isEqualTo(userId);
+ assertThat(user.getFirstName()).as("First name cannot be null.").isNotEmpty();
+ assertThat(user.getLastName()).as("Last name cannot be null.").isNotEmpty();
+ assertThat(user.getAvatar()).as("Avatar cannot be null.").isNotNull();
+ };
+ assertThat(existingUser).satisfies(responseRequirements);
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ @Test
+ void executePostRequest() {
+ try {
+ // prepare
+ @NonNull final User input = new User();
+ input.setFirstName("DummyFirst");
+ input.setLastName("DummyLast");
+
+ // execute
+ final User createdUser = userHttpRequestHelper.createUser(input);
+
+ // verify
+ final ThrowingConsumer responseRequirements =
+ user -> {
+ assertThat(user).as("Created user cannot be null.").isNotNull();
+ assertThat(user.getId()).as("ID should be positive number.").isPositive();
+ assertThat(user.getFirstName())
+ .as("First name does not match.")
+ .isEqualTo(input.getFirstName());
+ assertThat(user.getLastName())
+ .as("Last name does not match.")
+ .isEqualTo(input.getLastName());
+ assertThat(user.getCreatedAt()).as("Created at cannot be null.").isNotNull();
+ };
+ assertThat(createdUser).satisfies(responseRequirements);
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+
+ @Test
+ void executePutRequest() {
+ try {
+ // prepare
+ final int userId = 2;
+ @NonNull final User existingUser = userHttpRequestHelper.getUser(userId);
+ final String updatedFirstName = "UpdatedDummyFirst";
+ existingUser.setFirstName(updatedFirstName);
+ final String updatedLastName = "UpdatedDummyLast";
+ existingUser.setLastName(updatedLastName);
+
+ // execute
+ final User updatedUser = userHttpRequestHelper.updateUser(existingUser);
+
+ // verify
+ final ThrowingConsumer responseRequirements =
+ user -> {
+ assertThat(user).as("Updated user cannot be null.").isNotNull();
+ assertThat(user.getId())
+ .as("ID should be positive number.")
+ .isPositive()
+ .as("ID should not be updated.")
+ .isEqualTo(existingUser.getId());
+ assertThat(user.getFirstName())
+ .as("First name does not match.")
+ .isEqualTo(updatedFirstName);
+ assertThat(user.getLastName())
+ .as("Last name does not match.")
+ .isEqualTo(updatedLastName);
+ assertThat(user.getCreatedAt()).as("Created at cannot be null.").isNotNull();
+ };
+ assertThat(updatedUser).satisfies(responseRequirements);
+
+ } catch (Exception e) {
+ Assertions.fail("Failed to execute HTTP request.", e);
+ }
+ }
+}
diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java
new file mode 100644
index 000000000..a8c38bb0d
--- /dev/null
+++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java
@@ -0,0 +1,15 @@
+package io.refactoring.http5.client.example.util;
+
+import io.refactoring.http5.client.example.config.helper.HttpConfigurationHelper;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.junit.jupiter.api.Test;
+
+/** The Http configuration helper tests. */
+class HttpConfigurationHelperTests {
+ private final HttpConfigurationHelper httpConfigurationHelper = new HttpConfigurationHelper();
+ @Test
+ void getPooledCloseableHttpClient() {
+ final CloseableHttpClient httpClient = httpConfigurationHelper.getPooledCloseableHttpClient("localhost");
+
+ }
+}
diff --git a/apache-http-client/src/test/resources/application-test.properties b/apache-http-client/src/test/resources/application-test.properties
new file mode 100644
index 000000000..e607febf3
--- /dev/null
+++ b/apache-http-client/src/test/resources/application-test.properties
@@ -0,0 +1,3 @@
+app.prop.api-host=https://reqres.in
+app.prop.uri-users-api=/api/users
+app.prop.uri-users-api-page-size=12
\ No newline at end of file
diff --git a/apache-http-client/src/test/resources/logback-test.xml b/apache-http-client/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..eb0dfc545
--- /dev/null
+++ b/apache-http-client/src/test/resources/logback-test.xml
@@ -0,0 +1,16 @@
+
+
+
+ %date [%level] %logger - %msg %n
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/archunit/.gitignore b/archunit/.gitignore
new file mode 100644
index 000000000..5ff6309b7
--- /dev/null
+++ b/archunit/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/archunit/README.md b/archunit/README.md
new file mode 100644
index 000000000..29f29c2ab
--- /dev/null
+++ b/archunit/README.md
@@ -0,0 +1,5 @@
+# Examples for [Enforcing Your Architecture with ArchUnit](https://reflectoring.io/enforce-architecture-with-arch-unit)
+
+This repository contains the source code of the article's examples.
+The code examples and the code in the repository use Maven as a build tool and JUnit as testing framework.
+The only exception are the code examples for using ArchUnit with Scala.
diff --git a/archunit/pom.xml b/archunit/pom.xml
new file mode 100644
index 000000000..36afddd80
--- /dev/null
+++ b/archunit/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+ 4.0.0
+
+ io.reflectoring
+ archunit
+ 0.0.1-SNAPSHOT
+ ArchUnit
+ Demo project for ArchUnit
+
+
+ 19
+ 19
+ UTF-8
+
+
+
+
+ com.tngtech.archunit
+ archunit-junit5
+ 1.0.1
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.1
+ test
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.1.1
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.2
+ test
+
+
+
+
diff --git a/archunit/src/main/java/io/reflectoring/archunit/ArchUnitExamples.java b/archunit/src/main/java/io/reflectoring/archunit/ArchUnitExamples.java
new file mode 100644
index 000000000..e836d61ab
--- /dev/null
+++ b/archunit/src/main/java/io/reflectoring/archunit/ArchUnitExamples.java
@@ -0,0 +1,31 @@
+package io.reflectoring.archunit;
+
+import java.math.BigDecimal;
+import java.time.Clock;
+import java.time.LocalDateTime;
+
+public class ArchUnitExamples {
+
+ public void referenceDeprecatedClass() {
+ Dep dep = new Dep();
+ }
+
+ @Deprecated
+ public class Dep {
+
+ }
+
+ public void thisMethodCallsTheWrongBigDecimalConstructor() {
+ BigDecimal value = new BigDecimal(123.0);
+
+ // BigDecimal value = new BigDecimal("123.0"); // works!
+ }
+
+ public void instantiateLocalDatetime() {
+ var clock = Clock.systemDefaultZone();
+ LocalDateTime localDate = LocalDateTime.now(clock);
+ // The below line will fail the ArchUnit test
+ // LocalDateTime localDateWrong = LocalDateTime.now();
+ }
+
+}
diff --git a/archunit/src/main/java/io/reflectoring/archunit/api/EmployeeController.java b/archunit/src/main/java/io/reflectoring/archunit/api/EmployeeController.java
new file mode 100644
index 000000000..f96e7bd2a
--- /dev/null
+++ b/archunit/src/main/java/io/reflectoring/archunit/api/EmployeeController.java
@@ -0,0 +1,30 @@
+package io.reflectoring.archunit.api;
+
+import io.reflectoring.archunit.service.EmployeeResponse;
+import io.reflectoring.archunit.service.EmployeeService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+public class EmployeeController {
+
+ @GET()
+ @Path("/employees")
+ public EmployeeResponse getEmployee() {
+ EmployeeService service = new EmployeeService();
+ return service.getEmployee();
+ }
+
+ // Uncomment to cause ArchUnit rule violation
+// @GET()
+// @Path("/employees")
+// public EmployeeResponse getEmployee_withViolation() {
+// EmployeeDao dao = new EmployeeDao();
+// Employee employee = dao.findEmployee();
+// return new EmployeeResponse(
+// employee.id(),
+// employee.name(),
+// employee.active()
+// );
+// }
+}
diff --git a/archunit/src/main/java/io/reflectoring/archunit/persistence/Employee.java b/archunit/src/main/java/io/reflectoring/archunit/persistence/Employee.java
new file mode 100644
index 000000000..8b8fbe8f3
--- /dev/null
+++ b/archunit/src/main/java/io/reflectoring/archunit/persistence/Employee.java
@@ -0,0 +1,3 @@
+package io.reflectoring.archunit.persistence;
+
+public record Employee(long id, String name, boolean active) { }
diff --git a/archunit/src/main/java/io/reflectoring/archunit/persistence/EmployeeDao.java b/archunit/src/main/java/io/reflectoring/archunit/persistence/EmployeeDao.java
new file mode 100644
index 000000000..5ca702a69
--- /dev/null
+++ b/archunit/src/main/java/io/reflectoring/archunit/persistence/EmployeeDao.java
@@ -0,0 +1,8 @@
+package io.reflectoring.archunit.persistence;
+
+public class EmployeeDao {
+
+ public Employee findEmployee() {
+ return new Employee(1, "name", true);
+ }
+}
diff --git a/archunit/src/main/java/io/reflectoring/archunit/service/EmployeeResponse.java b/archunit/src/main/java/io/reflectoring/archunit/service/EmployeeResponse.java
new file mode 100644
index 000000000..d4ddef75b
--- /dev/null
+++ b/archunit/src/main/java/io/reflectoring/archunit/service/EmployeeResponse.java
@@ -0,0 +1,3 @@
+package io.reflectoring.archunit.service;
+
+public record EmployeeResponse(long id, String name, boolean active) { }
diff --git a/archunit/src/main/java/io/reflectoring/archunit/service/EmployeeService.java b/archunit/src/main/java/io/reflectoring/archunit/service/EmployeeService.java
new file mode 100644
index 000000000..2a0cbf684
--- /dev/null
+++ b/archunit/src/main/java/io/reflectoring/archunit/service/EmployeeService.java
@@ -0,0 +1,16 @@
+package io.reflectoring.archunit.service;
+
+import io.reflectoring.archunit.persistence.Employee;
+import io.reflectoring.archunit.persistence.EmployeeDao;
+
+public class EmployeeService {
+ public EmployeeResponse getEmployee() {
+ EmployeeDao employeeDao = new EmployeeDao();
+ Employee employee = employeeDao.findEmployee();
+ return new EmployeeResponse(
+ employee.id(),
+ employee.name(),
+ employee.active()
+ );
+ }
+}
diff --git a/archunit/src/test/java/io/reflectoring/archunit/ArchUnitCachedTest.java b/archunit/src/test/java/io/reflectoring/archunit/ArchUnitCachedTest.java
new file mode 100644
index 000000000..030f9cb9e
--- /dev/null
+++ b/archunit/src/test/java/io/reflectoring/archunit/ArchUnitCachedTest.java
@@ -0,0 +1,25 @@
+package io.reflectoring.archunit;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+import java.math.BigDecimal;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+ @AnalyzeClasses(packages = "io.reflectoring.archunit")
+ public class ArchUnitCachedTest {
+ @ArchTest
+ public void doNotCallDeprecatedMethodsFromTheProject(JavaClasses classes) {
+ ArchRule rule = noClasses().should().dependOnClassesThat().areAnnotatedWith(Deprecated.class);
+ rule.check(classes);
+ }
+
+ @ArchTest
+ public void doNotCallConstructorCached(JavaClasses classes) {
+ ArchRule rule = noClasses().should().callConstructor(BigDecimal.class, double.class);
+ rule.check(classes);
+ }
+ }
diff --git a/archunit/src/test/java/io/reflectoring/archunit/ArchUnitTest.java b/archunit/src/test/java/io/reflectoring/archunit/ArchUnitTest.java
new file mode 100644
index 000000000..c5a770752
--- /dev/null
+++ b/archunit/src/test/java/io/reflectoring/archunit/ArchUnitTest.java
@@ -0,0 +1,129 @@
+package io.reflectoring.archunit;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.domain.JavaMethod;
+import com.tngtech.archunit.core.domain.JavaMethodCall;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import com.tngtech.archunit.library.freeze.FreezingArchRule;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@AnalyzeClasses(packages = "io.reflectoring.archunit")
+public class ArchUnitTest {
+
+ @Test
+ public void myLayerAccessTest() {
+
+ JavaClasses importedClasses = new ClassFileImporter()
+ .importPackages("io.reflectoring.archunit.api");
+
+ ArchRule apiRule= noClasses()
+ .should()
+ .accessClassesThat()
+ .resideInAPackage("io.reflectoring.archunit.persistence");
+
+ apiRule.check(importedClasses);
+ }
+
+ @Test
+ public void freezingRules() {
+ JavaClasses importedClasses = new ClassFileImporter().importPackages("io.reflectoring.archunit");
+
+ ArchRule rule = methods().that()
+ .areAnnotatedWith(Test.class)
+ .should().haveFullNameNotMatching(".*\\d+.*");
+
+ FreezingArchRule.freeze(rule).check(importedClasses);
+ }
+
+ @Test
+ public void doNotCallConstructor() {
+ JavaClasses importedClasses = new ClassFileImporter()
+ .importPackages("io.reflectoring.archunit");
+ ArchRule rule = noClasses().should()
+ .callConstructor(BigDecimal.class, double.class);
+ rule.check(importedClasses);
+ }
+
+ @Test
+ public void instantiateLocalDateTimeWithClock() {
+ JavaClasses importedClasses = new ClassFileImporter().importPackages("io.reflectoring.archunit");
+ ArchRule rule = noClasses().should().callMethod(LocalDateTime.class, "now");
+ rule.check(importedClasses);
+ }
+
+ @Test
+ public void doNotCallDeprecatedMethodsFromTheProject() {
+ JavaClasses importedClasses = new ClassFileImporter()
+ .importPackages("io.reflectoring.archunit");
+ ArchRule rule = noClasses().should()
+ .dependOnClassesThat()
+ .areAnnotatedWith(Deprecated.class);
+ rule.check(importedClasses);
+ }
+
+ @Test
+ public void aTestWithAnAssertion() {
+ String expected = "chocolate";
+ String actual = "chocolate";
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void aTestWithoutAnAssertion() {
+ String expected = "chocolate";
+ String actual = "chocolate";
+ expected.equals(actual);
+ }
+
+ @ArchTest
+ public void testMethodsShouldAssertSomething(JavaClasses classes) {
+ ArchRule testMethodRule = methods().that().areAnnotatedWith(Test.class)
+ .should(callAnAssertion);
+ testMethodRule.check(classes);
+ }
+
+ @Test
+ public void testArrayList() {
+ List list = new ArrayList();
+ list.add("My item");
+ assertEquals(1, list.size());
+ }
+
+ public ArchCondition callAnAssertion =
+ new ArchCondition<>("a unit test should assert something") {
+ @Override
+ public void check(JavaMethod item, ConditionEvents events) {
+ for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
+ if((call.getTargetOwner().getPackageName().equals(
+ org.junit.jupiter.api.Assertions.class.getPackageName()
+ )
+ && call.getTargetOwner().getName().equals(
+ org.junit.jupiter.api.Assertions.class.getName()))
+ || (call.getTargetOwner().getName().equals(
+ com.tngtech.archunit.lang.ArchRule.class.getName())
+ && call.getName().equals("check"))
+ ) {
+ return;
+ }
+ }
+ events.add(SimpleConditionEvent.violated(
+ item, item.getDescription() + "does not assert anything.")
+ );
+ }
+ };
+ }
diff --git a/aws/aws-hello-world/README.md b/aws/aws-hello-world/README.md
new file mode 100644
index 000000000..9e8f49d57
--- /dev/null
+++ b/aws/aws-hello-world/README.md
@@ -0,0 +1,14 @@
+# AWS Hello World
+
+A simple Spring Boot application you can use to test deployments to AWS (or any other cloud provider, for that matter).
+
+This application is also available as a Docker image on Docker Hub: [https://hub.docker.com/r/reflectoring/aws-hello-world](https://hub.docker.com/r/reflectoring/aws-hello-world).
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [The AWS Journey Part 1: Deploying Your First Docker Image](https://reflectoring.io/aws-deploy-docker-image-via-web-console/)
+* [The AWS Journey Part 2: Deploying a Docker Image with AWS CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
+* [The AWS Journey Part 3: Connecting a Spring Boot Application to an RDS Instance with CloudFormation](https://reflectoring.io/aws-cloudformation-rds/)
+* [The AWS Journey Part 4: Zero-Downtime Deployment with CloudFormation and ECS](https://reflectoring.io/aws-cloudformation-ecs-deployment/)
diff --git a/aws/aws-rds-hello-world/README.md b/aws/aws-rds-hello-world/README.md
index c84a3d4ce..abe1d8f2f 100644
--- a/aws/aws-rds-hello-world/README.md
+++ b/aws/aws-rds-hello-world/README.md
@@ -23,3 +23,18 @@ Use the image instead of your real application to test AWS CloudFormation stacks
```
5. If the Spring Boot application can connect to the database, it will start up sucessfully and serve a message on the endpoint `/hello`.
+# AWS Hello World
+
+A simple Spring Boot application you can use to test deployments to AWS (or any other cloud provider, for that matter).
+
+This application is also available as a Docker image on Docker Hub: [https://hub.docker.com/r/reflectoring/aws-hello-world](https://hub.docker.com/r/reflectoring/aws-hello-world).
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [The AWS Journey Part 1: Deploying Your First Docker Image](https://reflectoring.io/aws-deploy-docker-image-via-web-console/)
+* [The AWS Journey Part 2: Deploying a Docker Image with AWS CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
+* [The AWS Journey Part 3: Connecting a Spring Boot Application to an RDS Instance with CloudFormation](https://reflectoring.io/aws-cloudformation-rds/)
+* [The AWS Journey Part 4: Zero-Downtime Deployment with CloudFormation and ECS](https://reflectoring.io/aws-cloudformation-ecs-deployment/)
+
diff --git a/aws/aws-terraform/README.md b/aws/aws-terraform/README.md
new file mode 100644
index 000000000..64cdffaba
--- /dev/null
+++ b/aws/aws-terraform/README.md
@@ -0,0 +1,11 @@
+# Terraform for creating AWS Resources
+
+Example code to create/update AWS resources with Terraform.
+Examples include Terraform capabilities of using modules, input variables and using Terraform cloud.
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Using Terraform to create AWS resources](https://reflectoring.io/terraform-aws/)
+
diff --git a/aws/aws-terraform/aws-app-stack-cloud/main.tf b/aws/aws-terraform/aws-app-stack-cloud/main.tf
new file mode 100644
index 000000000..52d2c3bcc
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/main.tf
@@ -0,0 +1,41 @@
+terraform {
+
+ backend "remote" {
+ hostname = "app.terraform.io"
+ organization = "pratikorg"
+ token = "pj7p59JFwSC4jQ.atlasv1.qfmTxLjTfaM5zKyaQrcGzuTojv6oCyLIoIAO7DkA2ieQY7OyINjINGGMiTczt62p1bs"
+ workspaces {
+ name = "my-tf-workspace"
+ }
+ }
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 3.36"
+ }
+ }
+}
+
+provider "aws" {
+ profile = "default"
+ region = "us-west-2"
+}
+
+module "app_server" {
+ source = "./modules/application"
+
+ ec2_instance_type = "t2.micro"
+ ami = "ami-830c94e3"
+ tags = {
+ Name = "server for web"
+ Env = "dev"
+ }
+}
+
+module "app_storage" {
+ source = "./modules/storage/"
+
+ bucket_name = "io.pratik.tf-example-bucket"
+ env = "dev"
+}
+
diff --git a/aws/aws-terraform/aws-app-stack-cloud/modules/application/main.tf b/aws/aws-terraform/aws-app-stack-cloud/modules/application/main.tf
new file mode 100644
index 000000000..21a255248
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/modules/application/main.tf
@@ -0,0 +1,5 @@
+resource "aws_instance" "vm-web" {
+ ami = var.ami
+ instance_type = var.ec2_instance_type
+ tags = var.tags
+}
diff --git a/aws/aws-terraform/aws-app-stack-cloud/modules/application/outputs.tf b/aws/aws-terraform/aws-app-stack-cloud/modules/application/outputs.tf
new file mode 100644
index 000000000..3a56539dc
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/modules/application/outputs.tf
@@ -0,0 +1,4 @@
+output "instanceID" {
+ description = "ID of ec2 instance"
+ value = aws_instance.vm-web.id
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-cloud/modules/application/variables.tf b/aws/aws-terraform/aws-app-stack-cloud/modules/application/variables.tf
new file mode 100644
index 000000000..a98430b13
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/modules/application/variables.tf
@@ -0,0 +1,16 @@
+variable "ec2_instance_type" {
+ description = "Instance type"
+ type = string
+}
+
+variable "ami" {
+ description = "ami id"
+ type = string
+}
+
+variable "tags" {
+ description = "Tags to set on the bucket."
+ type = map(string)
+ default = {Name = "server for web"
+ Env = "dev"}
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-cloud/modules/storage/main.tf b/aws/aws-terraform/aws-app-stack-cloud/modules/storage/main.tf
new file mode 100644
index 000000000..1b37e3899
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/modules/storage/main.tf
@@ -0,0 +1,9 @@
+resource "aws_s3_bucket" "s3_bucket" {
+ bucket = format("%s-%s",var.bucket_name,var.env)
+ acl = "private"
+
+ tags = {
+ Name = var.bucket_name
+ Environment = var.env
+ }
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-cloud/modules/storage/outputs.tf b/aws/aws-terraform/aws-app-stack-cloud/modules/storage/outputs.tf
new file mode 100644
index 000000000..247972b55
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/modules/storage/outputs.tf
@@ -0,0 +1,4 @@
+output "arn" {
+ description = "ARN of the bucket"
+ value = aws_s3_bucket.s3_bucket.arn
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-cloud/modules/storage/variables.tf b/aws/aws-terraform/aws-app-stack-cloud/modules/storage/variables.tf
new file mode 100644
index 000000000..568a0ffac
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-cloud/modules/storage/variables.tf
@@ -0,0 +1,13 @@
+# Input variable definitions
+
+variable "bucket_name" {
+ description = "Name of bucket"
+ type = string
+}
+
+variable "env" {
+ description = "Environment like dev, prod"
+ type = string
+}
+
+
diff --git a/aws/aws-terraform/aws-app-stack-input-vars/main.tf b/aws/aws-terraform/aws-app-stack-input-vars/main.tf
new file mode 100644
index 000000000..957949d2c
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-input-vars/main.tf
@@ -0,0 +1,24 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 3.27"
+ }
+ }
+}
+
+provider "aws" {
+ profile = "default"
+ region = "us-west-2"
+}
+
+resource "aws_instance" "vm-web" {
+ ami = "ami-830c94e3"
+ instance_type = var.ec2_instance_type
+
+ tags = {
+ Name = "server for web"
+ Env = "dev"
+ }
+}
+
diff --git a/aws/aws-terraform/aws-app-stack-input-vars/terraform.tfvars b/aws/aws-terraform/aws-app-stack-input-vars/terraform.tfvars
new file mode 100644
index 000000000..47f411cd2
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-input-vars/terraform.tfvars
@@ -0,0 +1 @@
+ec2_instance_type = "t2.micro"
diff --git a/aws/aws-terraform/aws-app-stack-input-vars/variables.tf b/aws/aws-terraform/aws-app-stack-input-vars/variables.tf
new file mode 100644
index 000000000..f96e9fbc6
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-input-vars/variables.tf
@@ -0,0 +1,4 @@
+variable "ec2_instance_type" {
+ description = "AWS EC2 instance type."
+ type = string
+}
diff --git a/aws/aws-terraform/aws-app-stack-modules/main.tf b/aws/aws-terraform/aws-app-stack-modules/main.tf
new file mode 100644
index 000000000..715ff2713
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/main.tf
@@ -0,0 +1,33 @@
+terraform {
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 3.36"
+ }
+ }
+}
+
+provider "aws" {
+ profile = "default"
+ region = "us-west-2"
+}
+
+module "app_server" {
+ source = "./modules/application"
+
+ ec2_instance_type = "t2.micro"
+ ami = "ami-830c94e3"
+ tags = {
+ Name = "server for web"
+ Env = "dev"
+ }
+}
+
+module "app_storage" {
+ source = "./modules/storage/"
+
+ bucket_name = "io.pratik.tf-example-bucket"
+ env = "dev"
+}
+
diff --git a/aws/aws-terraform/aws-app-stack-modules/modules/application/main.tf b/aws/aws-terraform/aws-app-stack-modules/modules/application/main.tf
new file mode 100644
index 000000000..21a255248
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/modules/application/main.tf
@@ -0,0 +1,5 @@
+resource "aws_instance" "vm-web" {
+ ami = var.ami
+ instance_type = var.ec2_instance_type
+ tags = var.tags
+}
diff --git a/aws/aws-terraform/aws-app-stack-modules/modules/application/outputs.tf b/aws/aws-terraform/aws-app-stack-modules/modules/application/outputs.tf
new file mode 100644
index 000000000..3a56539dc
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/modules/application/outputs.tf
@@ -0,0 +1,4 @@
+output "instanceID" {
+ description = "ID of ec2 instance"
+ value = aws_instance.vm-web.id
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-modules/modules/application/variables.tf b/aws/aws-terraform/aws-app-stack-modules/modules/application/variables.tf
new file mode 100644
index 000000000..a98430b13
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/modules/application/variables.tf
@@ -0,0 +1,16 @@
+variable "ec2_instance_type" {
+ description = "Instance type"
+ type = string
+}
+
+variable "ami" {
+ description = "ami id"
+ type = string
+}
+
+variable "tags" {
+ description = "Tags to set on the bucket."
+ type = map(string)
+ default = {Name = "server for web"
+ Env = "dev"}
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-modules/modules/storage/main.tf b/aws/aws-terraform/aws-app-stack-modules/modules/storage/main.tf
new file mode 100644
index 000000000..1b37e3899
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/modules/storage/main.tf
@@ -0,0 +1,9 @@
+resource "aws_s3_bucket" "s3_bucket" {
+ bucket = format("%s-%s",var.bucket_name,var.env)
+ acl = "private"
+
+ tags = {
+ Name = var.bucket_name
+ Environment = var.env
+ }
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-modules/modules/storage/outputs.tf b/aws/aws-terraform/aws-app-stack-modules/modules/storage/outputs.tf
new file mode 100644
index 000000000..247972b55
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/modules/storage/outputs.tf
@@ -0,0 +1,4 @@
+output "arn" {
+ description = "ARN of the bucket"
+ value = aws_s3_bucket.s3_bucket.arn
+}
\ No newline at end of file
diff --git a/aws/aws-terraform/aws-app-stack-modules/modules/storage/variables.tf b/aws/aws-terraform/aws-app-stack-modules/modules/storage/variables.tf
new file mode 100644
index 000000000..568a0ffac
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack-modules/modules/storage/variables.tf
@@ -0,0 +1,13 @@
+# Input variable definitions
+
+variable "bucket_name" {
+ description = "Name of bucket"
+ type = string
+}
+
+variable "env" {
+ description = "Environment like dev, prod"
+ type = string
+}
+
+
diff --git a/aws/aws-terraform/aws-app-stack/main.tf b/aws/aws-terraform/aws-app-stack/main.tf
new file mode 100644
index 000000000..0b655f3f1
--- /dev/null
+++ b/aws/aws-terraform/aws-app-stack/main.tf
@@ -0,0 +1,24 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 3.27"
+ }
+ }
+}
+
+provider "aws" {
+ profile = "default"
+ region = "us-west-2"
+}
+
+resource "aws_instance" "vm-web" {
+ ami = "ami-830c94e3"
+ instance_type = "t2.micro"
+
+ tags = {
+ Name = "server for web"
+ Env = "dev"
+ }
+}
+
diff --git a/aws/cdk/.mvn/wrapper/MavenWrapperDownloader.java b/aws/cdk/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/cdk/.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/aws/cdk/.mvn/wrapper/maven-wrapper.jar b/aws/cdk/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/cdk/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/cdk/.mvn/wrapper/maven-wrapper.properties b/aws/cdk/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/cdk/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/spring-cloud/sleuth-downstream-service/settings.gradle b/aws/cdk/mvnw
similarity index 100%
rename from spring-cloud/sleuth-downstream-service/settings.gradle
rename to aws/cdk/mvnw
diff --git a/aws/cdk/mvnw.cmd b/aws/cdk/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/cdk/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/aws/cdk/src/main/java/com/myorg/SecondStack.java b/aws/cdk/src/main/java/com/myorg/SecondStack.java
new file mode 100644
index 000000000..90038ceb3
--- /dev/null
+++ b/aws/cdk/src/main/java/com/myorg/SecondStack.java
@@ -0,0 +1,23 @@
+package com.myorg;
+
+import software.amazon.awscdk.core.Construct;
+import software.amazon.awscdk.core.Stack;
+import software.amazon.awscdk.core.StackProps;
+import software.amazon.awscdk.services.s3.Bucket;
+
+public class SecondStack extends Stack {
+ public SecondStack(final Construct scope, final String id) {
+ this(scope, id, null);
+ }
+
+ public SecondStack(final Construct scope, final String id, final StackProps props) {
+ super(scope, id, props);
+
+ String bucketName = (String) this.getNode().tryGetContext("bucketName2");
+
+ Bucket bucket = Bucket.Builder.create(this, "bucket")
+ .bucketName(bucketName)
+ .build();
+
+ }
+}
diff --git a/aws/cdkv2/.mvn/wrapper/MavenWrapperDownloader.java b/aws/cdkv2/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/cdkv2/.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/aws/cdkv2/.mvn/wrapper/maven-wrapper.jar b/aws/cdkv2/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/cdkv2/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/cdkv2/.mvn/wrapper/maven-wrapper.properties b/aws/cdkv2/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/cdkv2/.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/aws/cdkv2/README.md b/aws/cdkv2/README.md
new file mode 100644
index 000000000..4a8f03a80
--- /dev/null
+++ b/aws/cdkv2/README.md
@@ -0,0 +1,8 @@
+# Getting started with AWS CDK
+
+
+
+Blog posts about this topic:
+
+* [Getting started with AWS CDK](https://reflectoring.io/getting-started-with-aws-cdk/)
+
diff --git a/aws/cdkv2/cdk.context.json b/aws/cdkv2/cdk.context.json
new file mode 100644
index 000000000..31b26a725
--- /dev/null
+++ b/aws/cdkv2/cdk.context.json
@@ -0,0 +1,51 @@
+{
+ "vpc-provider:account=675153449441:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": {
+ "vpcId": "vpc-e5e76998",
+ "vpcCidrBlock": "172.31.0.0/16",
+ "availabilityZones": [],
+ "subnetGroups": [
+ {
+ "name": "Public",
+ "type": "Public",
+ "subnets": [
+ {
+ "subnetId": "subnet-d71e66f6",
+ "cidr": "172.31.80.0/20",
+ "availabilityZone": "us-east-1a",
+ "routeTableId": "rtb-ed425793"
+ },
+ {
+ "subnetId": "subnet-1783815a",
+ "cidr": "172.31.16.0/20",
+ "availabilityZone": "us-east-1b",
+ "routeTableId": "rtb-ed425793"
+ },
+ {
+ "subnetId": "subnet-71d8a72e",
+ "cidr": "172.31.32.0/20",
+ "availabilityZone": "us-east-1c",
+ "routeTableId": "rtb-ed425793"
+ },
+ {
+ "subnetId": "subnet-7ee39118",
+ "cidr": "172.31.0.0/20",
+ "availabilityZone": "us-east-1d",
+ "routeTableId": "rtb-ed425793"
+ },
+ {
+ "subnetId": "subnet-a56bed94",
+ "cidr": "172.31.48.0/20",
+ "availabilityZone": "us-east-1e",
+ "routeTableId": "rtb-ed425793"
+ },
+ {
+ "subnetId": "subnet-cc1e2ac2",
+ "cidr": "172.31.64.0/20",
+ "availabilityZone": "us-east-1f",
+ "routeTableId": "rtb-ed425793"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/aws/cdkv2/cdk.json b/aws/cdkv2/cdk.json
new file mode 100644
index 000000000..6f6e751d6
--- /dev/null
+++ b/aws/cdkv2/cdk.json
@@ -0,0 +1,27 @@
+{
+ "app": "mvn -e -q compile exec:java",
+ "watch": {
+ "include": [
+ "**"
+ ],
+ "exclude": [
+ "README.md",
+ "cdk*.json",
+ "target",
+ "pom.xml",
+ "src/test"
+ ]
+ },
+ "context": {
+ "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
+ "@aws-cdk/core:stackRelativeExports": true,
+ "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
+ "@aws-cdk/aws-lambda:recognizeVersionProps": true,
+ "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
+ "@aws-cdk/core:target-partitions": [
+ "aws",
+ "aws-cn"
+ ]
+ }
+}
diff --git a/aws/cdkv2/mvnw b/aws/cdkv2/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/cdkv2/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/aws/cdkv2/mvnw.cmd b/aws/cdkv2/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/cdkv2/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/aws/cdkv2/pom.xml b/aws/cdkv2/pom.xml
new file mode 100644
index 000000000..0909408e5
--- /dev/null
+++ b/aws/cdkv2/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ com.myorg
+ cdk-app
+ 0.1
+
+
+ UTF-8
+ 2.8.0
+ [10.0.0,11.0.0)
+ 5.7.1
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.0.0
+
+ com.myorg.CdkAppApp
+
+
+
+
+
+
+
+
+ software.amazon.awscdk
+ aws-cdk-lib
+ ${cdk.version}
+
+
+
+ software.constructs
+ constructs
+ ${constructs.version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+
diff --git a/aws/cdkv2/src/main/java/com/myorg/CdkAppApp.java b/aws/cdkv2/src/main/java/com/myorg/CdkAppApp.java
new file mode 100644
index 000000000..e3496f680
--- /dev/null
+++ b/aws/cdkv2/src/main/java/com/myorg/CdkAppApp.java
@@ -0,0 +1,25 @@
+package com.myorg;
+
+import software.amazon.awscdk.App;
+import software.amazon.awscdk.Environment;
+import software.amazon.awscdk.StackProps;
+
+import java.util.Arrays;
+
+public class CdkAppApp {
+ public static void main(final String[] args) {
+ App app = new App();
+
+ new CdkAppStack(app, "CdkAppStack", StackProps.builder()
+
+ .env(Environment.builder()
+ .account("**********")
+ .region("us-east-1")
+ .build())
+
+ .build());
+
+ app.synth();
+ }
+}
+
diff --git a/aws/cdkv2/src/main/java/com/myorg/CdkAppStack.java b/aws/cdkv2/src/main/java/com/myorg/CdkAppStack.java
new file mode 100644
index 000000000..dd6d668ef
--- /dev/null
+++ b/aws/cdkv2/src/main/java/com/myorg/CdkAppStack.java
@@ -0,0 +1,63 @@
+package com.myorg;
+
+import java.util.List;
+
+import org.jetbrains.annotations.NotNull;
+
+import software.amazon.awscdk.Stack;
+import software.amazon.awscdk.StackProps;
+import software.amazon.awscdk.services.ec2.BlockDeviceVolume;
+import software.amazon.awscdk.services.ec2.BlockDevice;
+import software.amazon.awscdk.services.ec2.IMachineImage;
+import software.amazon.awscdk.services.ec2.IVpc;
+import software.amazon.awscdk.services.ec2.Instance;
+import software.amazon.awscdk.services.ec2.InstanceClass;
+import software.amazon.awscdk.services.ec2.InstanceSize;
+import software.amazon.awscdk.services.ec2.InstanceType;
+import software.amazon.awscdk.services.ec2.MachineImage;
+import software.amazon.awscdk.services.ec2.SecurityGroup;
+import software.amazon.awscdk.services.ec2.Vpc;
+import software.amazon.awscdk.services.ec2.VpcLookupOptions;
+import software.constructs.Construct;
+// import software.amazon.awscdk.Duration;
+// import software.amazon.awscdk.services.sqs.Queue;
+
+public class CdkAppStack extends Stack {
+ public CdkAppStack(final Construct scope, final String id) {
+ this(scope, id, null);
+ }
+
+ public CdkAppStack(final Construct scope, final String id, final StackProps props) {
+ super(scope, id, props);
+
+
+
+ VpcLookupOptions options = VpcLookupOptions.builder().isDefault(true).build();
+ IVpc vpc = Vpc.fromLookup(this, "vpc", options);
+
+
+ SecurityGroup securityGroup = SecurityGroup
+ .Builder
+ .create(this, "sg")
+ .vpc(vpc)
+ .allowAllOutbound(true)
+ .build();
+
+
+
+ Instance.Builder.create(this, "Instance")
+ .vpc(vpc)
+ .instanceType(InstanceType.of(InstanceClass.BURSTABLE2, InstanceSize.MICRO))
+ .machineImage(MachineImage.latestAmazonLinux())
+ .blockDevices(List.of(BlockDevice.builder()
+ .deviceName("/dev/sda1")
+ .volume(BlockDeviceVolume.ebs(50))
+ .build(), BlockDevice.builder()
+ .deviceName("/dev/sdm")
+ .volume(BlockDeviceVolume.ebs(100))
+ .build()))
+ .securityGroup(securityGroup)
+ .build();
+
+ }
+}
diff --git a/aws/cdkv2/src/main/java/com/myorg/MyStorageBucket.java b/aws/cdkv2/src/main/java/com/myorg/MyStorageBucket.java
new file mode 100644
index 000000000..1d4b9bf44
--- /dev/null
+++ b/aws/cdkv2/src/main/java/com/myorg/MyStorageBucket.java
@@ -0,0 +1,34 @@
+/**
+ *
+ */
+package com.myorg;
+
+
+
+import software.amazon.awscdk.Duration;
+import software.amazon.awscdk.services.s3.Bucket;
+import software.amazon.awscdk.services.s3.LifecycleRule;
+import software.constructs.Construct;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class MyStorageBucket extends Construct{
+
+ public MyStorageBucket(final Construct scope, final String id) {
+ super(scope, id);
+ Bucket bucket = new Bucket(this, "mybucket");
+
+ LifecycleRule lifecycleRule = LifecycleRule.builder()
+ .abortIncompleteMultipartUploadAfter(Duration.minutes(30))
+ .enabled(false)
+ .expiration(Duration.minutes(30))
+ .expiredObjectDeleteMarker(false)
+ .id("id").build();
+
+ bucket.addLifecycleRule(lifecycleRule);
+
+ }
+
+}
diff --git a/aws/cdkv2/src/test/java/com/myorg/CdkAppTest.java b/aws/cdkv2/src/test/java/com/myorg/CdkAppTest.java
new file mode 100644
index 000000000..46b981cca
--- /dev/null
+++ b/aws/cdkv2/src/test/java/com/myorg/CdkAppTest.java
@@ -0,0 +1,26 @@
+// package com.myorg;
+
+// import software.amazon.awscdk.App;
+// import software.amazon.awscdk.assertions.Template;
+// import java.io.IOException;
+
+// import java.util.HashMap;
+
+// import org.junit.jupiter.api.Test;
+
+// example test. To run these tests, uncomment this file, along with the
+// example resource in java/src/main/java/com/myorg/CdkAppStack.java
+// public class CdkAppTest {
+
+// @Test
+// public void testStack() throws IOException {
+// App app = new App();
+// CdkAppStack stack = new CdkAppStack(app, "test");
+
+// Template template = Template.fromStack(stack);
+
+// template.hasResourceProperties("AWS::SQS::Queue", new HashMap() {{
+// put("VisibilityTimeout", 300);
+// }});
+// }
+// }
diff --git a/aws/cloudformation/ecs-in-two-public-subnets/README.md b/aws/cloudformation/ecs-in-two-public-subnets/README.md
index 011d51dd9..1a77a4c78 100644
--- a/aws/cloudformation/ecs-in-two-public-subnets/README.md
+++ b/aws/cloudformation/ecs-in-two-public-subnets/README.md
@@ -2,7 +2,12 @@

-# Companion Blog Post
+## Blog posts
-[The AWS Journey Part 2: Deploying a Docker image from the Command Line with CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
+Blog posts about this topic:
+
+* [The AWS Journey Part 1: Deploying Your First Docker Image](https://reflectoring.io/aws-deploy-docker-image-via-web-console/)
+* [The AWS Journey Part 2: Deploying a Docker Image with AWS CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
+* [The AWS Journey Part 3: Connecting a Spring Boot Application to an RDS Instance with CloudFormation](https://reflectoring.io/aws-cloudformation-rds/)
+* [The AWS Journey Part 4: Zero-Downtime Deployment with CloudFormation and ECS](https://reflectoring.io/aws-cloudformation-ecs-deployment/)
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/README.md b/aws/cloudformation/ecs-zero-downtime-deployment/README.md
index 011d51dd9..613ad4509 100644
--- a/aws/cloudformation/ecs-zero-downtime-deployment/README.md
+++ b/aws/cloudformation/ecs-zero-downtime-deployment/README.md
@@ -1,8 +1,15 @@
# Overview
-
+
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [The AWS Journey Part 1: Deploying Your First Docker Image](https://reflectoring.io/aws-deploy-docker-image-via-web-console/)
+* [The AWS Journey Part 2: Deploying a Docker Image with AWS CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
+* [The AWS Journey Part 3: Connecting a Spring Boot Application to an RDS Instance with CloudFormation](https://reflectoring.io/aws-cloudformation-rds/)
+* [The AWS Journey Part 4: Zero-Downtime Deployment with CloudFormation and ECS](https://reflectoring.io/aws-cloudformation-ecs-deployment/)
-# Companion Blog Post
-[The AWS Journey Part 2: Deploying a Docker image from the Command Line with CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/create-change-set.sh b/aws/cloudformation/ecs-zero-downtime-deployment/create-change-set.sh
new file mode 100755
index 000000000..24d4b4589
--- /dev/null
+++ b/aws/cloudformation/ecs-zero-downtime-deployment/create-change-set.sh
@@ -0,0 +1,18 @@
+# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
+export AWS_PAGER=""
+
+aws cloudformation create-change-set \
+ --change-set-name update-reflectoring-ecs-zero-downtime-deployment-service \
+ --stack-name reflectoring-ecs-zero-downtime-deployment-service \
+ --use-previous-template \
+ --parameters \
+ ParameterKey=StackName,ParameterValue=reflectoring-ecs-zero-downtime-deployment-network \
+ ParameterKey=ServiceName,ParameterValue=reflectoring-hello-world \
+ ParameterKey=ImageUrl,ParameterValue=docker.io/reflectoring/aws-hello-world:v4 \
+ ParameterKey=ContainerPort,ParameterValue=8080 \
+ ParameterKey=HealthCheckPath,ParameterValue=/hello \
+ ParameterKey=HealthCheckIntervalSeconds,ParameterValue=90
+
+aws cloudformation describe-change-set \
+ --stack-name reflectoring-ecs-zero-downtime-deployment-service \
+ --change-set-name update-reflectoring-ecs-zero-downtime-deployment-service
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/create.sh b/aws/cloudformation/ecs-zero-downtime-deployment/create.sh
old mode 100644
new mode 100755
index f3ea69360..d922abe82
--- a/aws/cloudformation/ecs-zero-downtime-deployment/create.sh
+++ b/aws/cloudformation/ecs-zero-downtime-deployment/create.sh
@@ -14,7 +14,7 @@ aws cloudformation create-stack \
--parameters \
ParameterKey=StackName,ParameterValue=reflectoring-ecs-zero-downtime-deployment-network \
ParameterKey=ServiceName,ParameterValue=reflectoring-hello-world \
- ParameterKey=ImageUrl,ParameterValue=docker.io/reflectoring/aws-hello-world:latest \
+ ParameterKey=ImageUrl,ParameterValue=docker.io/reflectoring/aws-hello-world:v3 \
ParameterKey=ContainerPort,ParameterValue=8080 \
ParameterKey=HealthCheckPath,ParameterValue=/hello \
ParameterKey=HealthCheckIntervalSeconds,ParameterValue=90
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/delete.sh b/aws/cloudformation/ecs-zero-downtime-deployment/delete.sh
old mode 100644
new mode 100755
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/execute-change-set.sh b/aws/cloudformation/ecs-zero-downtime-deployment/execute-change-set.sh
new file mode 100755
index 000000000..ffce10ddf
--- /dev/null
+++ b/aws/cloudformation/ecs-zero-downtime-deployment/execute-change-set.sh
@@ -0,0 +1,8 @@
+# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
+export AWS_PAGER=""
+
+aws cloudformation execute-change-set \
+ --stack-name reflectoring-ecs-zero-downtime-deployment-service \
+ --change-set-name update-reflectoring-ecs-zero-downtime-deployment-service
+
+aws cloudformation wait stack-update-complete --stack-name reflectoring-ecs-zero-downtime-deployment-service
\ No newline at end of file
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/service.yml b/aws/cloudformation/ecs-zero-downtime-deployment/service.yml
index 1357080f2..cb6553464 100644
--- a/aws/cloudformation/ecs-zero-downtime-deployment/service.yml
+++ b/aws/cloudformation/ecs-zero-downtime-deployment/service.yml
@@ -100,7 +100,7 @@ Resources:
Memory: !Ref 'ContainerMemory'
Image: !Ref 'ImageUrl'
PortMappings:
- - ContainerPort: !Ref 'ContainerPort' serve
+ - ContainerPort: !Ref 'ContainerPort'
LogConfiguration:
LogDriver: 'awslogs'
Options:
diff --git a/aws/cloudformation/ecs-zero-downtime-deployment/update.sh b/aws/cloudformation/ecs-zero-downtime-deployment/update.sh
new file mode 100755
index 000000000..dba0056f8
--- /dev/null
+++ b/aws/cloudformation/ecs-zero-downtime-deployment/update.sh
@@ -0,0 +1,17 @@
+# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
+export AWS_PAGER=""
+
+IMAGE_URL=$1
+
+aws cloudformation update-stack \
+ --stack-name reflectoring-ecs-zero-downtime-deployment-service \
+ --use-previous-template \
+ --parameters \
+ ParameterKey=StackName,ParameterValue=reflectoring-ecs-zero-downtime-deployment-network \
+ ParameterKey=ServiceName,ParameterValue=reflectoring-hello-world \
+ ParameterKey=ImageUrl,ParameterValue=$IMAGE_URL \
+ ParameterKey=ContainerPort,ParameterValue=8080 \
+ ParameterKey=HealthCheckPath,ParameterValue=/hello \
+ ParameterKey=HealthCheckIntervalSeconds,ParameterValue=90
+
+aws cloudformation wait stack-update-complete --stack-name reflectoring-ecs-zero-downtime-deployment-service
diff --git a/aws/kinesis/.mvn/wrapper/maven-wrapper.jar b/aws/kinesis/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..c1dd12f17
Binary files /dev/null and b/aws/kinesis/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/kinesis/.mvn/wrapper/maven-wrapper.properties b/aws/kinesis/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..e83fa6959
--- /dev/null
+++ b/aws/kinesis/.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.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/aws/kinesis/README.md b/aws/kinesis/README.md
new file mode 100644
index 000000000..1379116ff
--- /dev/null
+++ b/aws/kinesis/README.md
@@ -0,0 +1,8 @@
+# Getting started with AWS Kinesis
+
+
+
+Blog posts about this topic:
+
+* [Getting started with AWS Kinesis](https://reflectoring.io/getting-started-with-aws-kinesis/)
+
diff --git a/aws/kinesis/mvnw b/aws/kinesis/mvnw
new file mode 100755
index 000000000..5643201c7
--- /dev/null
+++ b/aws/kinesis/mvnw
@@ -0,0 +1,316 @@
+#!/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 /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
+ 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="`\\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
+
+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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$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 \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/kinesis/mvnw.cmd b/aws/kinesis/mvnw.cmd
new file mode 100644
index 000000000..23b7079a3
--- /dev/null
+++ b/aws/kinesis/mvnw.cmd
@@ -0,0 +1,188 @@
+@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 "%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 DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq 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%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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 "%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/kinesis/pom.xml b/aws/kinesis/pom.xml
new file mode 100644
index 000000000..245aea8ca
--- /dev/null
+++ b/aws/kinesis/pom.xml
@@ -0,0 +1,72 @@
+
+ 4.0.0
+ io.pratik
+ kinesisexamples
+ 0.0.1-SNAPSHOT
+
+ 1.8
+ 1.8
+
+
+
+
+ software.amazon.awssdk
+ kinesis
+
+
+
+ software.amazon.awssdk
+ firehose
+
+
+ software.amazon.awssdk
+ kinesisanalyticsv2
+
+
+ software.amazon.awssdk
+ kinesisvideo
+
+
+ software.amazon.awssdk
+ kinesisvideomedia
+
+
+ com.amazonaws
+ amazon-kinesis-video-streams-producer-sdk-java
+ 1.12.0
+
+
+
+ org.apache.flink
+ flink-streaming-java_2.11
+ 1.14.3
+ provided
+
+
+ org.apache.flink
+ flink-clients_2.11
+ 1.14.3
+
+
+ software.amazon.kinesis
+ amazon-kinesis-connector-flink
+ 2.3.0
+
+
+
+
+
+ software.amazon.awssdk
+ bom
+ 2.17.116
+ pom
+ import
+
+
+
+
\ No newline at end of file
diff --git a/aws/kinesis/src/main/java/io/pratik/Constants.java b/aws/kinesis/src/main/java/io/pratik/Constants.java
new file mode 100644
index 000000000..3b7768b11
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/Constants.java
@@ -0,0 +1,16 @@
+/**
+ *
+ */
+package io.pratik;
+
+import software.amazon.awssdk.regions.Region;
+
+/**
+ * @author pratikdas
+ *
+ */
+public interface Constants {
+ String MY_DATA_STREAM = "mydatastream";
+ String AWS_PROFILE_NAME = "tompoc";
+ Region AWS_REGION = Region.AP_SOUTHEAST_2;
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/DataStreamResourceHelper.java b/aws/kinesis/src/main/java/io/pratik/DataStreamResourceHelper.java
new file mode 100644
index 000000000..fbfb08f99
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/DataStreamResourceHelper.java
@@ -0,0 +1,79 @@
+/**
+ *
+ */
+package io.pratik;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.kinesis.KinesisClient;
+import software.amazon.awssdk.services.kinesis.model.CreateStreamRequest;
+import software.amazon.awssdk.services.kinesis.model.CreateStreamResponse;
+import software.amazon.awssdk.services.kinesis.model.DescribeStreamSummaryRequest;
+import software.amazon.awssdk.services.kinesis.model.DescribeStreamSummaryResponse;
+import software.amazon.awssdk.services.kinesis.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.kinesis.model.StreamDescriptionSummary;
+import software.amazon.awssdk.services.kinesis.model.StreamMode;
+import software.amazon.awssdk.services.kinesis.model.StreamModeDetails;
+import software.amazon.awssdk.services.kinesis.model.StreamStatus;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class DataStreamResourceHelper {
+
+
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ createDataStream();
+
+ }
+
+
+ public static void createDataStream() {
+ KinesisClient kinesisClient = getKinesisClient();
+
+ CreateStreamRequest createStreamRequest = CreateStreamRequest.builder().streamName(Constants.MY_DATA_STREAM).streamModeDetails(StreamModeDetails.builder().streamMode(StreamMode.ON_DEMAND).build()).build();
+ CreateStreamResponse createStreamResponse = kinesisClient.createStream(createStreamRequest);
+
+ DescribeStreamSummaryRequest describeStreamSummaryRequest = DescribeStreamSummaryRequest.builder().streamName(Constants.MY_DATA_STREAM ).build();
+ DescribeStreamSummaryResponse describeStreamSummaryResponse = kinesisClient.describeStreamSummary(describeStreamSummaryRequest );
+
+
+ long startTime = System.currentTimeMillis();
+ long endTime = startTime + ( 10 * 60 * 1000 );
+ while ( System.currentTimeMillis() < endTime ) {
+ try {
+ Thread.sleep(20 * 1000);
+ }
+ catch ( Exception e ) {}
+
+ try {
+ StreamDescriptionSummary streamDescSumm = describeStreamSummaryResponse.streamDescriptionSummary();
+
+ if(streamDescSumm.streamStatus().equals(StreamStatus.ACTIVE)) break;
+ try {
+ Thread.sleep( 1000 );
+ }catch ( Exception e ) {}
+ }catch ( ResourceNotFoundException e ) {}
+
+
+ }
+
+ }
+
+ private static KinesisClient getKinesisClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create(Constants.AWS_PROFILE_NAME);
+
+ KinesisClient kinesisClient = KinesisClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return kinesisClient;
+ }
+
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/EventConsumer.java b/aws/kinesis/src/main/java/io/pratik/EventConsumer.java
new file mode 100644
index 000000000..a12de619a
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/EventConsumer.java
@@ -0,0 +1,79 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.kinesis.KinesisClient;
+import software.amazon.awssdk.services.kinesis.model.GetRecordsRequest;
+import software.amazon.awssdk.services.kinesis.model.GetRecordsResponse;
+import software.amazon.awssdk.services.kinesis.model.GetShardIteratorRequest;
+import software.amazon.awssdk.services.kinesis.model.GetShardIteratorResponse;
+import software.amazon.awssdk.services.kinesis.model.Record;
+import software.amazon.awssdk.services.kinesis.model.ShardIteratorType;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class EventConsumer {
+ private static final Logger logger = Logger.getLogger(EventConsumer.class.getName());
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ receiveEvents();
+
+ }
+
+ public static void receiveEventsWithKCL() {}
+
+
+
+ public static void receiveEvents() {
+ KinesisClient kinesisClient = getKinesisClient();
+
+ String shardId = "shardId-000000000001";
+
+ GetShardIteratorRequest getShardIteratorRequest = GetShardIteratorRequest.builder().streamName(Constants.MY_DATA_STREAM).shardId(shardId).shardIteratorType(ShardIteratorType.TRIM_HORIZON.name()).build();
+ GetShardIteratorResponse getShardIteratorResponse = kinesisClient.getShardIterator(getShardIteratorRequest );
+ String shardIterator = getShardIteratorResponse.shardIterator();
+ logger.info("shardIterator " + shardIterator);
+
+ while(shardIterator != null) {
+ GetRecordsRequest getRecordsRequest = GetRecordsRequest.builder().shardIterator(shardIterator).limit(5).build();
+ GetRecordsResponse getRecordsResponse = kinesisClient.getRecords(getRecordsRequest );
+
+
+ List records = getRecordsResponse.records();
+
+ logger.info("count "+records.size());
+ records.forEach(record->{
+ byte[] dataInBytes = record.data().asByteArray();
+ logger.info(new String(dataInBytes));
+ });
+
+ shardIterator = getRecordsResponse.nextShardIterator();
+ }
+
+ kinesisClient.close();
+ }
+
+ private static KinesisClient getKinesisClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create(Constants.AWS_PROFILE_NAME);
+
+ KinesisClient kinesisClient = KinesisClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1)
+ .build();
+ return kinesisClient;
+ }
+
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/EventSender.java b/aws/kinesis/src/main/java/io/pratik/EventSender.java
new file mode 100644
index 000000000..cbc3753c8
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/EventSender.java
@@ -0,0 +1,104 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.kinesis.KinesisClient;
+import software.amazon.awssdk.services.kinesis.model.PutRecordRequest;
+import software.amazon.awssdk.services.kinesis.model.PutRecordResponse;
+import software.amazon.awssdk.services.kinesis.model.PutRecordsRequest;
+import software.amazon.awssdk.services.kinesis.model.PutRecordsRequestEntry;
+import software.amazon.awssdk.services.kinesis.model.PutRecordsResponse;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class EventSender {
+
+ private static final Logger logger = Logger.getLogger(EventSender.class.getName());
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ sendEvent();
+
+ }
+
+ public static void sendEvent() {
+ KinesisClient kinesisClient = getKinesisClient();
+
+ String partitionKey = String.format("partitionKey-%d", 1);
+ SdkBytes data = SdkBytes.fromByteBuffer(ByteBuffer.wrap("Test data".getBytes()));
+ PutRecordRequest putRecordRequest
+ = PutRecordRequest
+ .builder()
+ .streamName(Constants.MY_DATA_STREAM)
+ .partitionKey(partitionKey)
+ .data(data)
+ .build();
+
+ PutRecordResponse putRecordResult
+ = kinesisClient.putRecord(putRecordRequest);
+
+ logger.info("Put Result" + putRecordResult);
+ kinesisClient.close();
+ }
+
+ public static void sendEvents() {
+ KinesisClient kinesisClient = getKinesisClient();
+
+ String partitionKey = String.format("partitionKey-%d", 1);
+
+
+ List putRecordsRequestEntryList = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ SdkBytes data = SdkBytes
+ .fromByteBuffer(ByteBuffer.wrap(("Test event "+i).getBytes()));
+
+ PutRecordsRequestEntry putRecordsRequestEntry
+ = PutRecordsRequestEntry.builder()
+
+ .data(data)
+ .partitionKey(partitionKey)
+ .build();
+
+ putRecordsRequestEntryList.add(putRecordsRequestEntry);
+ }
+
+
+ PutRecordsRequest putRecordsRequest
+ = PutRecordsRequest
+ .builder()
+ .streamName(Constants.MY_DATA_STREAM)
+ .records(putRecordsRequestEntryList)
+ .build();
+
+ PutRecordsResponse putRecordsResult = kinesisClient
+ .putRecords(putRecordsRequest);
+
+ logger.info("Put Result" + putRecordsResult);
+ kinesisClient.close();
+ }
+
+ private static KinesisClient getKinesisClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create(Constants.AWS_PROFILE_NAME);
+
+ KinesisClient kinesisClient = KinesisClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return kinesisClient;
+ }
+
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/FirehoseEventSender.java b/aws/kinesis/src/main/java/io/pratik/FirehoseEventSender.java
new file mode 100644
index 000000000..dcef4bc13
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/FirehoseEventSender.java
@@ -0,0 +1,63 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.firehose.FirehoseClient;
+import software.amazon.awssdk.services.firehose.model.PutRecordRequest;
+import software.amazon.awssdk.services.firehose.model.PutRecordResponse;
+import software.amazon.awssdk.services.firehose.model.Record;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class FirehoseEventSender {
+ private final static Logger logger = Logger.getLogger(FirehoseEventSender.class.getName());
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ new FirehoseEventSender().sendEvent();
+
+ }
+
+ public void sendEvent() {
+ String deliveryStreamName= "PUT-S3-5ZGgA";
+
+ String data = "Test data" + "\n";
+ ;
+ Record record = Record.builder().data(SdkBytes.fromByteArray(data.getBytes())).build();
+
+ PutRecordRequest putRecordRequest = PutRecordRequest
+ .builder()
+ .deliveryStreamName(deliveryStreamName).record(record).build();
+
+ FirehoseClient firehoseClient = getFirehoseClient();
+ // Put record into the DeliveryStream
+ PutRecordResponse putRecordResponse = firehoseClient.putRecord(putRecordRequest);
+
+ logger.info("record ID:: " + putRecordResponse.recordId());
+ firehoseClient.close();
+ }
+
+ private static FirehoseClient getFirehoseClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create(Constants.AWS_PROFILE_NAME);
+
+ FirehoseClient kinesisClient = FirehoseClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Constants.AWS_REGION).build();
+ return kinesisClient;
+ }
+
+
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/FirehoseResourceHelper.java b/aws/kinesis/src/main/java/io/pratik/FirehoseResourceHelper.java
new file mode 100644
index 000000000..72e280779
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/FirehoseResourceHelper.java
@@ -0,0 +1,84 @@
+/**
+ *
+ */
+package io.pratik;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.firehose.FirehoseClient;
+import software.amazon.awssdk.services.firehose.model.CreateDeliveryStreamRequest;
+import software.amazon.awssdk.services.firehose.model.CreateDeliveryStreamResponse;
+import software.amazon.awssdk.services.firehose.model.DeliveryStreamType;
+import software.amazon.awssdk.services.firehose.model.ExtendedS3DestinationConfiguration;
+import software.amazon.awssdk.services.firehose.model.KinesisStreamSourceConfiguration;
+import software.amazon.awssdk.services.firehose.model.S3DestinationConfiguration;
+import software.amazon.awssdk.services.kinesis.KinesisClient;
+import software.amazon.awssdk.services.kinesis.model.CreateStreamRequest;
+import software.amazon.awssdk.services.kinesis.model.CreateStreamResponse;
+import software.amazon.awssdk.services.kinesis.model.DescribeStreamSummaryRequest;
+import software.amazon.awssdk.services.kinesis.model.DescribeStreamSummaryResponse;
+import software.amazon.awssdk.services.kinesis.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.kinesis.model.StreamDescriptionSummary;
+import software.amazon.awssdk.services.kinesis.model.StreamMode;
+import software.amazon.awssdk.services.kinesis.model.StreamModeDetails;
+import software.amazon.awssdk.services.kinesis.model.StreamStatus;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class FirehoseResourceHelper {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ createDeliveryStream();
+
+ }
+
+
+ public static void createDeliveryStream() {
+ FirehoseClient firehoseClient = getFirehoseClient();
+
+ String kinesisStreamARN = "";
+
+ String roleARN = "";
+ KinesisStreamSourceConfiguration kinesisStreamSourceConfiguration =
+ KinesisStreamSourceConfiguration.builder().build();
+
+ String bucketARN = "";
+ ExtendedS3DestinationConfiguration s3DestinationConfiguration
+ = ExtendedS3DestinationConfiguration.builder()
+ .bucketARN(bucketARN)
+ .build();
+
+ String streamName = "";
+ CreateDeliveryStreamRequest createDeliveryStreamRequest =
+ CreateDeliveryStreamRequest
+ .builder()
+ .deliveryStreamName(streamName )
+ .deliveryStreamType(DeliveryStreamType.DIRECT_PUT)
+ .kinesisStreamSourceConfiguration(kinesisStreamSourceConfiguration )
+ .extendedS3DestinationConfiguration(s3DestinationConfiguration )
+ .build();
+ CreateDeliveryStreamResponse response = firehoseClient.createDeliveryStream(createDeliveryStreamRequest );
+ firehoseClient.close();
+ }
+
+ private static FirehoseClient getFirehoseClient() {
+ AwsCredentialsProvider credentialsProvider =
+ ProfileCredentialsProvider.create(Constants.AWS_PROFILE_NAME);
+
+ FirehoseClient firehoseClient =
+ FirehoseClient.builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1)
+ .build();
+
+
+ return firehoseClient;
+ }
+
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/StreamingDataGenerator.java b/aws/kinesis/src/main/java/io/pratik/StreamingDataGenerator.java
new file mode 100644
index 000000000..d87998816
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/StreamingDataGenerator.java
@@ -0,0 +1,64 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class StreamingDataGenerator {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ try {
+ new StreamingDataGenerator().generate();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ private void generate() throws Exception {
+ File file = getFile("apache_access_log");
+
+ // Note: Double backquote is to avoid compiler
+ // interpret words
+ // like \test as \t (ie. as a escape sequence)
+
+ // Creating an object of BufferedReader class
+ BufferedReader br
+ = new BufferedReader(new FileReader(file));
+
+ // Declaring a string variable
+ String st;
+ // Condition holds true till
+ // there is character in a string
+ while ((st = br.readLine()) != null) {
+ System.out.println(st);
+ break;
+ }
+ }
+
+ private File getFile(String fileName) throws IOException
+ {
+ ClassLoader classLoader = getClass().getClassLoader();
+ URL resource = classLoader.getResource(fileName);
+
+ if (resource == null) {
+ throw new IllegalArgumentException("file is not found!");
+ } else {
+ return new File(resource.getFile());
+ }
+ }
+
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/flink/ErrorCounter.java b/aws/kinesis/src/main/java/io/pratik/flink/ErrorCounter.java
new file mode 100644
index 000000000..b2fe14d5b
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/flink/ErrorCounter.java
@@ -0,0 +1,153 @@
+package io.pratik.flink;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import org.apache.flink.api.common.functions.FilterFunction;
+import org.apache.flink.api.common.functions.FlatMapFunction;
+import org.apache.flink.api.common.serialization.SimpleStringSchema;
+import org.apache.flink.streaming.api.datastream.DataStream;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.util.Collector;
+
+import io.pratik.Constants;
+import io.pratik.models.LogRecord;
+import software.amazon.kinesis.connectors.flink.FlinkKinesisProducer;
+import software.amazon.kinesis.connectors.flink.config.ConsumerConfigConstants;
+
+public class ErrorCounter {
+ private final static Logger logger = Logger.getLogger(ErrorCounter.class.getName());
+ private static final String FILE_PATH = "/Users/pratikdas/eclipse-workspace/kinesisexamples/src/main/resources/apache_access_log";
+
+ public static void main(String[] args) throws Exception {
+ // set up the streaming execution environment
+ final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+
+ DataStream inputStream = createSource(env);
+
+ DataStream logRecords = mapStringToLogRecord(inputStream);
+
+ DataStream errorRecords = filterErrorRecords(logRecords);
+
+ DataStream keyedStream = assignIPasKey(errorRecords);
+
+
+ DataStream keyedStreamAsText = mapLogRecordToString(keyedStream);
+
+ //TODO Uncomment this code for deploying to Kinesis Data Analytics
+
+ // keyedStream.addSink(createSink());
+
+ keyedStreamAsText.print();
+
+ env.execute("Error alerts");
+
+ }
+
+
+
+ private static DataStream mapLogRecordToString(DataStream keyedStream) {
+ DataStream keyedStreamAsText = keyedStream.flatMap(new FlatMapFunction() {
+
+ @Override
+ public void flatMap(LogRecord value, Collector out) throws Exception {
+ out.collect(value.getUrl()+"::" + value.getHttpStatus());
+ }
+ });
+ return keyedStreamAsText;
+ }
+
+
+
+ private static DataStream assignIPasKey(DataStream errorRecords) {
+ DataStream keyedStream = errorRecords.keyBy(value -> value.getIp());
+ return keyedStream;
+ }
+
+
+
+ private static DataStream filterErrorRecords(DataStream logRecords) {
+ DataStream errorRecords = logRecords.filter(new FilterFunction() {
+
+ @Override
+ public boolean filter(LogRecord value) throws Exception {
+ boolean matched = !value.getHttpStatus().equalsIgnoreCase("200");
+
+ return matched;
+ }
+ });
+ return errorRecords;
+ }
+
+
+
+ private static DataStream mapStringToLogRecord(DataStream inputStream) {
+ DataStream logRecords = inputStream.flatMap(new FlatMapFunction() {
+
+ @Override
+ public void flatMap(String value, Collector out) throws Exception {
+
+ String[] parts = value.split("\\s+");
+
+ LogRecord record = new LogRecord();
+ record.setIp(parts[0]);
+ record.setHttpStatus(parts[8]);
+ record.setUrl(parts[6]);
+
+ out.collect(record);
+
+ }
+
+ });
+ return logRecords;
+ }
+
+
+
+ /*private static void createSink(final StreamExecutionEnvironment env, DataStream input) {
+ input.print();
+ }*/
+
+ private static DataStream createSource(final StreamExecutionEnvironment env) {
+ return env.readTextFile(
+ FILE_PATH);
+ }
+
+ //TODO Uncomment this code for deploying to Kinesis Data Analytics
+ /*private static DataStream createSource(final StreamExecutionEnvironment env) {
+ Properties inputProperties = new Properties();
+ inputProperties.setProperty(ConsumerConfigConstants.AWS_REGION, Constants.AWS_REGION.toString());
+ inputProperties.setProperty(ConsumerConfigConstants.STREAM_INITIAL_POSITION, "LATEST");
+
+ String inputStreamName = "in-app-log-stream";
+ return env.addSource(new FlinkKinesisConsumer<>(inputStreamName, new SimpleStringSchema(), inputProperties));
+ }*/
+
+ private static FlinkKinesisProducer createSink() {
+ Properties outputProperties = new Properties();
+ outputProperties.setProperty(ConsumerConfigConstants.AWS_REGION, Constants.AWS_REGION.toString());
+
+ FlinkKinesisProducer sink = new FlinkKinesisProducer<>(new SimpleStringSchema(), outputProperties);
+ String outputStreamName = "log_data_stream";
+ sink.setDefaultStream(outputStreamName);
+ sink.setDefaultPartition("0");
+
+ return sink;
+ }
+
+
+
+ private File getFile(String fileName) throws IOException {
+ ClassLoader classLoader = getClass().getClassLoader();
+ URL resource = classLoader.getResource(fileName);
+
+ if (resource == null) {
+ throw new IllegalArgumentException("file is not found!");
+ } else {
+ return new File(resource.getFile());
+ }
+ }
+}
diff --git a/aws/kinesis/src/main/java/io/pratik/models/LogRecord.java b/aws/kinesis/src/main/java/io/pratik/models/LogRecord.java
new file mode 100644
index 000000000..560d8915d
--- /dev/null
+++ b/aws/kinesis/src/main/java/io/pratik/models/LogRecord.java
@@ -0,0 +1,40 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class LogRecord {
+ private String ip;
+ private String httpStatus;
+ private String url;
+ public String getIp() {
+ return ip;
+ }
+ public void setIp(String ip) {
+ this.ip = ip;
+ }
+ public String getHttpStatus() {
+ return httpStatus;
+ }
+ public void setHttpStatus(String httpStatus) {
+ this.httpStatus = httpStatus;
+ }
+ public String getUrl() {
+ return url;
+ }
+ public void setUrl(String url) {
+ this.url = url;
+ }
+ @Override
+ public String toString() {
+ return "LogRecord [ip=" + ip + ", httpStatus=" + httpStatus + ", url=" + url + "]";
+ }
+
+
+
+
+}
diff --git a/aws/kinesis/src/main/resources/apache_access_log b/aws/kinesis/src/main/resources/apache_access_log
new file mode 100644
index 000000000..3d8d626ca
--- /dev/null
+++ b/aws/kinesis/src/main/resources/apache_access_log
@@ -0,0 +1,33 @@
+83.149.9.216 - - [17/May/2015:10:05:03 +0000] "GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1" 200 203023 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:43 +0000] "GET /presentations/logstash-monitorama-2013/images/kibana-dashboard3.png HTTP/1.1" 200 171717 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:47 +0000] "GET /presentations/logstash-monitorama-2013/plugin/highlight/highlight.js HTTP/1.1" 200 26185 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:12 +0000] "GET /presentations/logstash-monitorama-2013/plugin/zoom-js/zoom.js HTTP/1.1" 200 7697 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:07 +0000] "GET /presentations/logstash-monitorama-2013/plugin/notes/notes.js HTTP/1.1" 200 2892 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:34 +0000] "GET /presentations/logstash-monitorama-2013/images/sad-medic.png HTTP/1.1" 200 430406 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:57 +0000] "GET /presentations/logstash-monitorama-2013/css/fonts/Roboto-Bold.ttf HTTP/1.1" 404 38720 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:50 +0000] "GET /presentations/logstash-monitorama-2013/css/fonts/Roboto-Regular.ttf HTTP/1.1" 200 41820 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+83.149.9.216 - - [17/May/2015:10:05:24 +0000] "GET /presentations/logstash-monitorama-2013/images/frontend-response-codes.png HTTP/1.1" 310 52878 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
+46.105.14.53 - - [17/May/2015:10:05:44 +0000] "GET /blog/tags/puppet?flav=rss20 HTTP/1.1" 200 14872 "-" "UniversalFeedParser/4.2-pre-314-svn +http://feedparser.org/"
+20.241.23.22 - - [17/May/2015:10:05:47 +0000] "GET /blog/geekery/soekris-gpio.html HTTP/1.0" 200 9587 "http://www.semicomplete.com/blog/tags/C" "Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)"
+91.177.205.119 - - [17/May/2015:10:05:22 +0000] "GET /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "http://en.wikipedia.org/wiki/Xvfb" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
+91.177.205.119 - - [17/May/2015:10:05:34 +0000] "GET /reset.css HTTP/1.1" 200 1015 "http://semicomplete.com/blog/geekery/xvfb-firefox.html" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
+91.177.205.119 - - [17/May/2015:10:05:37 +0000] "GET /style2.css HTTP/1.1" 200 4877 "http://semicomplete.com/blog/geekery/xvfb-firefox.html" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
+91.177.205.119 - - [17/May/2015:10:05:54 +0000] "GET /images/jordan-80.png HTTP/1.1" 200 6146 "http://semicomplete.com/blog/geekery/xvfb-firefox.html" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
+91.177.205.119 - - [17/May/2015:10:05:31 +0000] "GET /images/web/2009/banner.png HTTP/1.1" 200 52315 "http://semicomplete.com/blog/geekery/xvfb-firefox.html" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
+91.177.205.119 - - [17/May/2015:10:05:32 +0000] "GET /favicon.ico HTTP/1.1" 200 3638 "-" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Win64; x64; Trident/6.0)"
+66.249.73.185 - - [17/May/2015:10:05:22 +0000] "GET /doc/index.html?org/elasticsearch/action/search/SearchResponse.html HTTP/1.1" 404 294 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
+207.241.237.228 - - [17/May/2015:10:05:40 +0000] "GET /blog/tags/defcon HTTP/1.0" 200 24142 "http://www.semicomplete.com/blog/tags/C" "Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)"
+207.241.237.101 - - [17/May/2015:10:05:51 +0000] "GET /blog/tags/regex HTTP/1.0" 200 14888 "http://www.semicomplete.com/blog/tags/C" "Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)"
+87.169.99.232 - - [17/May/2015:10:05:59 +0000] "GET /presentations/puppet-at-loggly/puppet-at-loggly.pdf.html HTTP/1.1" 200 24747 "https://www.google.de/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"
+209.85.238.199 - - [17/May/2015:10:05:30 +0000] "GET /blog/tags/firefox?flav=rss20 HTTP/1.1" 200 16021 "-" "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; 3 subscribers; feed-id=14171215010336145331)"
+209.85.238.199 - - [17/May/2015:10:05:15 +0000] "GET /test.xml HTTP/1.1" 200 1370 "-" "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; 1 subscribers; feed-id=11390274670024826467)"
+81.220.24.207 - - [17/May/2015:10:05:13 +0000] "GET /blog/geekery/ssl-latency.html HTTP/1.1" 200 17147 "http://www.google.fr/url?sa=t&rct=j&q=&esrc=s&source=web&cd=5&ved=0CE4QFjAE&url=http%3A%2F%2Fwww.semicomplete.com%2Fblog%2Fgeekery%2Fssl-latency.html&ei=ZdEAU9mGGuWX1AW09IDoBw&usg=AFQjCNHw6zioJpizqX8Q0YpKKaF4zdCSEg&bvm=bv.61535280,d.d2k" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"
+81.220.24.207 - - [17/May/2015:10:05:44 +0000] "GET /reset.css HTTP/1.1" 200 1015 "http://www.semicomplete.com/blog/geekery/ssl-latency.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"
+81.220.24.207 - - [17/May/2015:10:05:26 +0000] "GET /images/jordan-80.png HTTP/1.1" 200 6146 "http://www.semicomplete.com/blog/geekery/ssl-latency.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"
+81.220.24.207 - - [17/May/2015:10:05:39 +0000] "GET /style2.css HTTP/1.1" 200 4877 "http://www.semicomplete.com/blog/geekery/ssl-latency.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"
+81.220.24.207 - - [17/May/2015:10:05:52 +0000] "GET /images/web/2009/banner.png HTTP/1.1" 200 52315 "http://www.semicomplete.com/blog/geekery/ssl-latency.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"
+81.220.24.207 - - [17/May/2015:10:05:21 +0000] "GET /favicon.ico HTTP/1.1" 200 3638 "http://www.semicomplete.com/blog/geekery/ssl-latency.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"
+66.249.73.135 - - [17/May/2015:11:05:17 +0000] "GET /blog/geekery/vmware-cpu-performance.html HTTP/1.1" 200 12908 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
+46.105.14.53 - - [17/May/2015:11:05:42 +0000] "GET /blog/tags/puppet?flav=rss20 HTTP/1.1" 200 14872 "-" "UniversalFeedParser/4.2-pre-314-svn +http://feedparser.org/"
+218.30.103.62 - - [17/May/2015:11:05:11 +0000] "GET /robots.txt HTTP/1.1" 200 - "-" "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)"
+218.30.103.62 - - [17/May/2015:11:05:46 +0000] "GET /robots.txt HTTP/1.1" 200 - "-" "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)"
\ No newline at end of file
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/localstack/.DS_Store b/aws/localstack/.DS_Store
new file mode 100644
index 000000000..3903c8630
Binary files /dev/null and b/aws/localstack/.DS_Store differ
diff --git a/aws/localstack/.mvn/wrapper/MavenWrapperDownloader.java b/aws/localstack/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..e76d1f324
--- /dev/null
+++ b/aws/localstack/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/aws/localstack/.mvn/wrapper/maven-wrapper.jar b/aws/localstack/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/localstack/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/localstack/.mvn/wrapper/maven-wrapper.properties b/aws/localstack/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/localstack/.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/aws/localstack/Dockerfile b/aws/localstack/Dockerfile
new file mode 100644
index 000000000..f9d5aa163
--- /dev/null
+++ b/aws/localstack/Dockerfile
@@ -0,0 +1,7 @@
+FROM openjdk:8-jre-alpine
+MAINTAINER pratikdas@yahoo.com
+RUN addgroup -S spring && adduser -S spring -G spring
+USER spring:spring
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/aws/localstack/HELP.md b/aws/localstack/HELP.md
new file mode 100644
index 000000000..ed579dfcc
--- /dev/null
+++ b/aws/localstack/HELP.md
@@ -0,0 +1,10 @@
+# Usage Examples of LocalStack
+
+
+### Reference Documentation
+
+Two examples of using LocalStack is provided here. Make sure you have Docker installed and docker engine is started. The examples use Java 14. Make changes to pom.xml if you are using a lower version of Java.
+
+* JUnit Test classes : JUnit Jupiter tests start LocalStack in a Docker container when the test runs and stops the container when test ends.
+* Spring Boot application: REST API for creating a customer profile in AWS DynamoDB and store profile picture in S3.
+
diff --git a/aws/localstack/README.md b/aws/localstack/README.md
new file mode 100644
index 000000000..21d777eb6
--- /dev/null
+++ b/aws/localstack/README.md
@@ -0,0 +1,10 @@
+# Localstack
+
+Example code to test against AWS services locally using Localstack.
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Local Development with AWS on LocalStack](https://reflectoring.io/aws-localstack/)
+
diff --git a/aws/localstack/docker-compose.yml b/aws/localstack/docker-compose.yml
new file mode 100644
index 000000000..1178686b2
--- /dev/null
+++ b/aws/localstack/docker-compose.yml
@@ -0,0 +1,21 @@
+version: '2.1'
+
+services:
+ localstack:
+ container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
+ image: localstack/localstack
+ ports:
+ - "4566-4599:4566-4599"
+ - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"
+ environment:
+ - SERVICES=s3,dynamodb,cloudformation
+ - DEBUG=${DEBUG- }
+ - DATA_DIR=${DATA_DIR- }
+ - PORT_WEB_UI=${PORT_WEB_UI- }
+ - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
+ - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
+ - DOCKER_HOST=unix:///var/run/docker.sock
+ - HOST_TMP_FOLDER=${TMPDIR}
+ volumes:
+ - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
+ - "/var/run/docker.sock:/var/run/docker.sock"
\ No newline at end of file
diff --git a/aws/localstack/mvnw b/aws/localstack/mvnw
new file mode 100755
index 000000000..a16b5431b
--- /dev/null
+++ b/aws/localstack/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/aws/localstack/mvnw.cmd b/aws/localstack/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/aws/localstack/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 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 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/aws/localstack/pom.xml b/aws/localstack/pom.xml
new file mode 100644
index 000000000..bd8060870
--- /dev/null
+++ b/aws/localstack/pom.xml
@@ -0,0 +1,84 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.3.1.RELEASE
+
+
+ io.pratik
+ customerregistration
+ 1.0
+ customerregistration
+ Spring Boot with Dynamodb and S3 to demonstrate LocalStack
+
+
+ 13
+
+
+
+
+ software.amazon.awssdk
+ bom
+ 2.9.1
+ pom
+ import
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ software.amazon.awssdk
+ dynamodb
+
+
+ software.amazon.awssdk
+ s3
+
+
+ cloud.localstack
+ localstack-utils
+ 0.2.1
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/aws/localstack/sample.yaml b/aws/localstack/sample.yaml
new file mode 100644
index 000000000..add243fc5
--- /dev/null
+++ b/aws/localstack/sample.yaml
@@ -0,0 +1,22 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: A sample template for creating a stack with a bucket and a DynamoDB table.
+Resources:
+ S3BucketForPoc:
+ Type: AWS::S3::Bucket
+ Properties:
+ BucketName: io.pratik.profileimages
+ DynamoDBTable:
+ Type: AWS::DynamoDB::Table
+ Properties:
+ TableName: entities
+ AttributeDefinitions:
+ -
+ AttributeName: "pk"
+ AttributeType: "S"
+ KeySchema:
+ -
+ AttributeName: "pk"
+ KeyType: "HASH"
+ ProvisionedThroughput:
+ ReadCapacityUnits: 5
+ WriteCapacityUnits: 5
\ No newline at end of file
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/CustomerregistrationApplication.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/CustomerregistrationApplication.java
new file mode 100644
index 000000000..f40cece1f
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/CustomerregistrationApplication.java
@@ -0,0 +1,13 @@
+package io.reflectoring.customerregistration;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CustomerregistrationApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CustomerregistrationApplication.class, args);
+ }
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/controllers/CustomerController.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/controllers/CustomerController.java
new file mode 100644
index 000000000..252e2371c
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/controllers/CustomerController.java
@@ -0,0 +1,54 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.controllers;
+
+import io.reflectoring.customerregistration.dtos.CustomerCreateResponse;
+import io.reflectoring.customerregistration.dtos.CustomerDto;
+import io.reflectoring.customerregistration.services.CustomerService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.reflectoring.customerregistration.dtos.CustomerCreateRequest;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@RestController
+@RequestMapping("/customers")
+@Slf4j
+public class CustomerController {
+
+ private CustomerService customerService;
+
+ public CustomerController(CustomerService customerService) {
+ super();
+ this.customerService = customerService;
+ }
+
+ @PostMapping("/")
+ @ResponseBody
+ public CustomerCreateResponse registerCustomer(@RequestBody final CustomerCreateRequest request) {
+ String customerID = customerService.createCustomer(request);
+ return CustomerCreateResponse.builder()
+ .customerID(customerID)
+ .build();
+
+ }
+
+ @GetMapping("/{customerID}")
+ @ResponseBody
+ public CustomerDto getCustomerByID(@PathVariable("customerID") String customerID) {
+ log.info("g=fetching customer with id {}", customerID);
+ return customerService.fetchCustomer(customerID);
+ }
+
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/AddressDto.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/AddressDto.java
new file mode 100644
index 000000000..5dabdd892
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/AddressDto.java
@@ -0,0 +1,20 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.dtos;
+
+import lombok.Data;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Data
+public class AddressDto {
+ private String premiseNumber;
+ private String streetName;
+ private String city;
+ private String countryName;
+ private String zip;
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerCreateRequest.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerCreateRequest.java
new file mode 100644
index 000000000..e68ff1544
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerCreateRequest.java
@@ -0,0 +1,26 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.dtos;
+
+import java.util.List;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Data
+@Builder
+public class CustomerCreateRequest {
+
+ private String firstName;
+ private String lastName;
+ private String email;
+ private String phoneNumber;
+ private String gender;
+ private String photo;
+ private List addresses;
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerCreateResponse.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerCreateResponse.java
new file mode 100644
index 000000000..2d72a5978
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerCreateResponse.java
@@ -0,0 +1,19 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.dtos;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Data
+@Builder
+public class CustomerCreateResponse {
+
+ private String customerID;
+ private String error;
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerDto.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerDto.java
new file mode 100644
index 000000000..00a722538
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/dtos/CustomerDto.java
@@ -0,0 +1,28 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.dtos;
+
+import java.util.List;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Data
+@Builder
+public class CustomerDto {
+ private String customerID;
+ private String userName;
+ private String dateOfBirth;
+ private String email;
+ private String firstName;
+ private String lastName;
+ private String phoneNumber;
+ private String gender;
+ private String photo;
+ private List addresses;
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Address.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Address.java
new file mode 100644
index 000000000..d49ab32e4
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Address.java
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.models;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class Address {
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Credentials.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Credentials.java
new file mode 100644
index 000000000..ea5e4a40b
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Credentials.java
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.models;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class Credentials {
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Customer.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Customer.java
new file mode 100644
index 000000000..6ccff842a
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/models/Customer.java
@@ -0,0 +1,35 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.models;
+
+import java.io.Serializable;
+import java.util.List;
+
+import lombok.Builder;
+import lombok.Data;
+
+
+
+/**
+ * @author Pratik Das
+ *
+ */
+
+@Data
+@Builder
+public class Customer implements Serializable{
+
+ private static final long serialVersionUID = 1L;
+ private String userName;
+ private String dateOfBirth;
+ private String email;
+ private String firstName;
+ private String lastName;
+ private String phoneNumber;
+ private String gender;
+ private String photo;
+ private List addresses;
+ private Credentials password;
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/repositories/CustomerImageStore.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/repositories/CustomerImageStore.java
new file mode 100644
index 000000000..685a7f99a
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/repositories/CustomerImageStore.java
@@ -0,0 +1,84 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.repositories;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Paths;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import io.reflectoring.customerregistration.dtos.CustomerCreateRequest;
+import lombok.extern.slf4j.Slf4j;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.core.sync.ResponseTransformer;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.S3ClientBuilder;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Service
+@Slf4j
+public class CustomerImageStore {
+ private static final Region region = Region.US_EAST_1;
+ private static final String BUCKET_NAME = "io.pratik.profileimages";
+
+ private final String awsEndpoint;
+
+ public CustomerImageStore(@Value("${aws.local.endpoint:#{null}}") String awsEndpoint) {
+ super();
+ this.awsEndpoint = awsEndpoint;
+ }
+
+ private S3Client getS3Client() {
+ S3Client s3 = null;;
+ try {
+ S3ClientBuilder builder = S3Client.builder();
+ // awsEndpoint is set only in local environments
+ if(awsEndpoint != null) {
+ // override aws endpoint with localstack URL in dev environment
+ builder.endpointOverride(new URI(awsEndpoint));
+ }
+ s3 = builder.region(region).build();
+ }catch(URISyntaxException ex) {
+ log.error("Invalid url {}",awsEndpoint);
+ throw new IllegalStateException("Invalid url "+awsEndpoint,ex);
+ }
+ return s3;
+ }
+
+ /**
+ * Fetch profile image from s3 bucket
+ * @param customerDto
+ * @param key
+ * @return customer ID generated using uuid
+ */
+ public void fetchProfileImage(final CustomerCreateRequest customerDto, String key) {
+ S3Client s3 = getS3Client();
+ if(s3 != null) {
+ GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(BUCKET_NAME).key(key).build();
+ s3.getObject(getObjectRequest);
+ s3.getObject(GetObjectRequest.builder().bucket(BUCKET_NAME).key(key).build(),
+ ResponseTransformer.toFile(Paths.get("image"+key)));
+ }
+ }
+
+ public void saveImage(final CustomerCreateRequest customerDto, final String imageKey) {
+ S3Client s3 = getS3Client();
+ if(s3 != null) {
+ // Put Object
+ PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(BUCKET_NAME).key(imageKey).build();
+ RequestBody requestBody = RequestBody.fromString(customerDto.getPhoto());
+ s3.putObject(putObjectRequest, requestBody);
+ }
+ }
+
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/repositories/CustomerProfileStore.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/repositories/CustomerProfileStore.java
new file mode 100644
index 000000000..535a996b7
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/repositories/CustomerProfileStore.java
@@ -0,0 +1,140 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.repositories;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import io.reflectoring.customerregistration.dtos.CustomerCreateRequest;
+import io.reflectoring.customerregistration.dtos.CustomerDto;
+import lombok.extern.slf4j.Slf4j;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
+import software.amazon.awssdk.services.dynamodb.model.AttributeAction;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValueUpdate;
+import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
+import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
+import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
+import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Slf4j
+@Service
+public class CustomerProfileStore {
+ private static final String TABLE_NAME = "entities";
+ private static final Region region = Region.US_EAST_1;
+
+ private final String awsEndpoint;
+
+ public CustomerProfileStore(@Value("${aws.local.endpoint:#{null}}") String awsEndpoint) {
+ super();
+ this.awsEndpoint = awsEndpoint;
+ }
+
+ private DynamoDbClient getDdbClient() {
+ DynamoDbClient dynamoDB = null;;
+ try {
+ DynamoDbClientBuilder builder = DynamoDbClient.builder();
+ // awsLocalEndpoint is set only in local environments
+ if(awsEndpoint != null) {
+ // override aws endpoint with localstack URL in dev environment
+ builder.endpointOverride(new URI(awsEndpoint));
+ }
+ dynamoDB = builder.region(region).build();
+ }catch(URISyntaxException ex) {
+ log.error("Invalid url {}",awsEndpoint);
+ throw new IllegalStateException("Invalid url "+awsEndpoint,ex);
+ }
+ return dynamoDB;
+ }
+
+ /**
+ * Store profile data in dynamodb table
+ * @param customerDto
+ * @param key
+ * @return customer ID generated using uuid
+ */
+ public CustomerDto fetchProfile( String customerID) {
+
+ DynamoDbClient ddb = getDdbClient();
+
+ Map attributeKey = new HashMap<>();
+ String key = "CUSTOMER:"+customerID;
+ attributeKey.put("pk", AttributeValue.builder().s(key).build());
+ GetItemRequest getItemRequest = GetItemRequest.builder().tableName(TABLE_NAME).key(attributeKey).build();
+ GetItemResponse getItemResponse = ddb.getItem(getItemRequest);
+ Map responseAttributeMap = getItemResponse.item();
+ return CustomerDto.builder()
+ .customerID(customerID)
+ .firstName(responseAttributeMap.get("fname").s())
+ .lastName(responseAttributeMap.get("lname").s())
+ .email(responseAttributeMap.get("email").s())
+ .phoneNumber(responseAttributeMap.get("phone").s())
+ .build();
+ }
+
+ /**
+ * Store profile data in dynamodb table
+ * @param customerDto
+ * @return customer ID generated using uuid
+ */
+ public String createProfile(final CustomerCreateRequest customerDto) {
+ DynamoDbClient ddb = getDdbClient();
+
+ HashMap itemKey = new HashMap();
+
+ String customerID = UUID.randomUUID().toString();
+ String key = "CUSTOMER:"+customerID;
+ itemKey.put("pk", AttributeValue.builder().s(key).build());
+
+ HashMap updatedValues =
+ new HashMap();
+
+ // Update the column specified by name with updatedVal
+ updatedValues.put("fname", AttributeValueUpdate.builder()
+ .value(AttributeValue.builder().s(customerDto.getFirstName()).build())
+ .action(AttributeAction.PUT)
+ .build());
+
+ updatedValues.put("lname", AttributeValueUpdate.builder()
+ .value(AttributeValue.builder().s(customerDto.getLastName()).build())
+ .action(AttributeAction.PUT)
+ .build());
+
+ updatedValues.put("phone", AttributeValueUpdate.builder()
+ .value(AttributeValue.builder().s(customerDto.getPhoneNumber()).build())
+ .action(AttributeAction.PUT)
+ .build());
+
+ updatedValues.put("email", AttributeValueUpdate.builder()
+ .value(AttributeValue.builder().s(customerDto.getEmail()).build())
+ .action(AttributeAction.PUT)
+ .build());
+
+ UpdateItemRequest request = UpdateItemRequest.builder()
+ .tableName(TABLE_NAME)
+ .key(itemKey)
+ .attributeUpdates(updatedValues)
+ .build();
+
+ try {
+ ddb.updateItem(request);
+ } catch (DynamoDbException e) {
+ }
+ return customerID;
+
+ }
+
+}
diff --git a/aws/localstack/src/main/java/io/reflectoring/customerregistration/services/CustomerService.java b/aws/localstack/src/main/java/io/reflectoring/customerregistration/services/CustomerService.java
new file mode 100644
index 000000000..c99d242fb
--- /dev/null
+++ b/aws/localstack/src/main/java/io/reflectoring/customerregistration/services/CustomerService.java
@@ -0,0 +1,47 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration.services;
+
+import io.reflectoring.customerregistration.dtos.CustomerDto;
+import io.reflectoring.customerregistration.repositories.CustomerImageStore;
+import io.reflectoring.customerregistration.repositories.CustomerProfileStore;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import io.reflectoring.customerregistration.dtos.CustomerCreateRequest;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Service
+@Slf4j
+public class CustomerService {
+
+
+ private CustomerImageStore customerImageStore;
+ private CustomerProfileStore customerProfileStore;
+
+
+ @Autowired
+ public CustomerService(CustomerImageStore customerImageStore, CustomerProfileStore customerProfileStore) {
+ super();
+ this.customerImageStore = customerImageStore;
+ this.customerProfileStore = customerProfileStore;
+ }
+
+ public CustomerDto fetchCustomer(final String customerID) {
+ return customerProfileStore.fetchProfile(customerID);
+
+ }
+
+
+ public String createCustomer(final CustomerCreateRequest request) {
+ String customerKey = customerProfileStore.createProfile(request);
+ customerImageStore.saveImage(request,customerKey);
+ return customerKey;
+ }
+
+}
diff --git a/aws/localstack/src/main/resources/application-local.properties b/aws/localstack/src/main/resources/application-local.properties
new file mode 100644
index 000000000..3bf46073d
--- /dev/null
+++ b/aws/localstack/src/main/resources/application-local.properties
@@ -0,0 +1 @@
+aws.local.endpoint=http://localhost:4566
diff --git a/aws/localstack/src/main/resources/application.properties b/aws/localstack/src/main/resources/application.properties
new file mode 100644
index 000000000..cd2d02be7
--- /dev/null
+++ b/aws/localstack/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port=8085
diff --git a/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java
new file mode 100644
index 000000000..3f0690801
--- /dev/null
+++ b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java
@@ -0,0 +1,103 @@
+/**
+ *
+ */
+package io.reflectoring.customerregistration;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import io.reflectoring.customerregistration.dtos.CustomerCreateRequest;
+import io.reflectoring.customerregistration.repositories.CustomerImageStore;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import cloud.localstack.Localstack;
+import cloud.localstack.docker.LocalstackDockerExtension;
+import cloud.localstack.docker.annotation.LocalstackDockerProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.test.context.ActiveProfiles;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Slf4j
+@ExtendWith(LocalstackDockerExtension.class)
+@ActiveProfiles("local")
+@LocalstackDockerProperties(services = { "s3" })
+@Disabled(value = "Disabled because starting Docker containers like this does not run on GitHub Actions.")
+class CustomerImageStoreTest {
+
+ private static final Region region = Region.US_EAST_1;
+ private static final String BUCKET_NAME = "io.pratik.profileimages";
+
+ private CustomerImageStore customerImageStore = null;
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @BeforeEach
+ void setUp() throws Exception {
+ createBucket();
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testStoreImage() {
+ customerImageStore = new CustomerImageStore(Localstack.INSTANCE.getEndpointS3());
+ String photo = "test image";
+ CustomerCreateRequest customerDto = CustomerCreateRequest.builder().firstName("pratik").photo(photo ).build();
+ String imageKey = customerDto.getFirstName()+System.currentTimeMillis();
+ customerImageStore.saveImage(customerDto, imageKey );
+ assertTrue(keyExistsInBucket(imageKey),"Object created");
+ }
+
+ private void createBucket() {
+ URI localEndpoint = null;
+ try {
+ localEndpoint = new URI(Localstack.INSTANCE.getEndpointS3());
+ S3Client s3 = S3Client.builder().endpointOverride(localEndpoint).region(region).build();
+ CreateBucketRequest createBucketRequest = CreateBucketRequest.builder().bucket(BUCKET_NAME).build();
+ s3.createBucket(createBucketRequest );
+
+ } catch (URISyntaxException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ private boolean keyExistsInBucket(final String objectKey ) {
+ URI localEndpoint = null;
+ try {
+ localEndpoint = new URI(Localstack.INSTANCE.getEndpointS3());
+ S3Client s3 = S3Client.builder().endpointOverride(localEndpoint).region(region).build();
+ GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(BUCKET_NAME).key(objectKey).build();
+ s3.getObject(getObjectRequest);
+ return true;
+ } catch (URISyntaxException e) {
+ log.error("Invalid url {}",localEndpoint);
+ } catch(NoSuchKeyException ex) {
+ log.error("Key does not exist {}", objectKey);
+ }
+ return false;
+
+ }
+
+}
diff --git a/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java
new file mode 100644
index 000000000..fbbbaac05
--- /dev/null
+++ b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java
@@ -0,0 +1,105 @@
+package io.reflectoring.customerregistration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import io.reflectoring.customerregistration.dtos.CustomerDto;
+import io.reflectoring.customerregistration.repositories.CustomerProfileStore;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+
+import cloud.localstack.Localstack;
+import cloud.localstack.docker.LocalstackDockerExtension;
+import cloud.localstack.docker.annotation.LocalstackDockerProperties;
+import io.reflectoring.customerregistration.dtos.CustomerCreateRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.test.context.ActiveProfiles;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
+import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
+import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
+import software.amazon.awssdk.services.dynamodb.model.KeyType;
+import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
+import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
+
+/**
+ * @author Pratik Das
+ *
+ */
+@Slf4j
+@ExtendWith(LocalstackDockerExtension.class)
+@ActiveProfiles("local")
+@LocalstackDockerProperties(services = { "dynamodb"})
+@Disabled(value = "Disabled because starting Docker containers like this does not run on GitHub Actions.")
+class CustomerProfileStoreTest {
+
+ private static final Region region = Region.US_EAST_1;
+ private static final String TABLE_NAME = "entities";
+
+ private CustomerProfileStore customerProfileStore = null;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ customerProfileStore = new CustomerProfileStore(Localstack.INSTANCE.getEndpointDynamoDB());
+ createTable();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testCreateCustomerProfile() {
+ CustomerCreateRequest customerDto = CustomerCreateRequest.builder()
+ .firstName("pratik")
+ .lastName("das")
+ .phoneNumber("657576")
+ .email("prat@gmail.co")
+ .gender("M")
+ .build();
+
+ String customerID = customerProfileStore.createProfile(customerDto );
+ log.info("customer ID {}", customerID);
+ assertTrue(customerID != null, "Item created");
+ CustomerDto dto = customerProfileStore.fetchProfile(customerID);
+ assertEquals("pratik", dto.getFirstName(), "first name matched");
+ assertEquals("das", dto.getLastName(), "last name matched");
+ }
+
+ private void createTable() {
+ try {
+ DynamoDbClient ddbClient = DynamoDbClient.builder().endpointOverride(new URI(Localstack.INSTANCE.getEndpointDynamoDB())).region(region).build();
+
+ String key = "pk";
+ CreateTableRequest createTableRequest = CreateTableRequest.builder()
+ .attributeDefinitions(AttributeDefinition.builder()
+ .attributeName(key )
+ .attributeType(ScalarAttributeType.S)
+ .build())
+ .keySchema(KeySchemaElement.builder()
+ .attributeName(key)
+ .keyType(KeyType.HASH)
+ .build())
+ .provisionedThroughput(ProvisionedThroughput.builder()
+ .readCapacityUnits(10l)
+ .writeCapacityUnits(10l)
+ .build())
+ .tableName(TABLE_NAME)
+ .build();
+
+ ddbClient.createTable(createTableRequest);
+ } catch (URISyntaxException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/aws/s3/.gitignore b/aws/s3/.gitignore
new file mode 100644
index 000000000..140947a93
--- /dev/null
+++ b/aws/s3/.gitignore
@@ -0,0 +1,141 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/java,intellij+all,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=java,intellij+all,gradle
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+# out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+all Patch ###
+# Ignores the whole .idea folder and all .iml files
+# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
+
+.idea/
+
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Gradle ###
+.gradle
+build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+# End of https://www.toptal.com/developers/gitignore/api/java,intellij+all,gradle
+
+# Dumps of the meta-db
+data/*.db
\ No newline at end of file
diff --git a/aws/s3/Readme.md b/aws/s3/Readme.md
new file mode 100644
index 000000000..43d48f38f
--- /dev/null
+++ b/aws/s3/Readme.md
@@ -0,0 +1,167 @@
+
+
+
+
+
Private File Upload
+
+
+ Build on Spring Cloud & AWS S3
+
+
+
+
+
+
+## Table of Contents
+
+* [About the Project](#about-the-project)
+ * [Built With](#built-with)
+* [Getting Started](#getting-started)
+ * [Prerequisites](#prerequisites)
+ * [Installation](#installation)
+* [Usage](#usage)
+* [Contact](#contact)
+* [Acknowledgements](#acknowledgements)
+
+
+
+
+## About The Project
+
+
+
+
+
+
+### Built With
+- Spring Boot
+- Spring Cloud
+- AWS S3
+- ☕️ & ❤️
+
+
+## Getting Started
+
+### Prerequisites
+
+In order to run this application you should have an Amazon Web Services (AWS) account.
+
+### Installation
+
+1. Update the `application.yaml` settings value for `cloud.aws.credentials.profile-name` to the name of your local AWS-profile
+2. run `./gradlew bootRun`
+3. Open your browser at `localhost:8080`
+
+
+## Usage
+
+The application wraps a lot of S3-related API calls in easy to use UI elements to
+
+- create/delete buckets (mapped as "spaces" so we can give them non-unique names)
+- upload/delete files
+- make files public/private
+- create pre-signed URLs for files
+- create bucket policy rules to set the lifecycle duration for elements
+
+More detailed instructions can be found here:
+
+
+ 🪣 Create a bucket
+
+1. Navigate to the _Spaces_ section
+2. Click on _New Space_
+3. Enter the name and click _Submit_
+4. A message should pop up to indicate success
+
+
+
+
+ 🗂 Upload a File
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. Click on _Upload File_
+4. Pick our file, provide a name and click _Submit_
+5. A message should pop up to indicate success
+
+
+
+ 🔎 List all Objects in a Bucket
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. You see a list of all objects stored in the bucket
+
+
+
+ 🌐 Get an Object's URL
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. Select _Download_ on the target object
+4. The object's URL shall be opened in a new tab
+
+
+
+ 📢 Make an object public
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. Select _Make Public_ on the target object
+4. A message should pop up to indicate success
+
+
+
+ 🤫 Make an Object private
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. Select _Make Private_ on the target object
+4. A message should pop up to indicate success
+
+
+
+ 🔥 Delete an Object
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. Select _Delete_ on the target object
+4. The list of objects should reload without the deleted one
+
+
+
+ ☄️ Delete a Bucket
+
+1. Navigate to the _Spaces_ section
+2. Select _Delete_ on the target Space/Bucket
+3. The list of buckets should reload without the deleted one
+
+
+
+ 👾 Generate a pre-signed URL
+
+1. Navigate to the _Spaces_ section
+2. Select _Details_ on the target Space/Bucket
+3. Select _Magic Link_ on the target object
+4. A message should pop up, containing a pre-signed URL for that object (which is valid for 15 minutes)
+
+
+
+ ⏳ Set Expiration on Bucket
+
+1. Navigate to the _Spaces_ section
+2. Select _Make Temporary_ on the target Space/Bucket
+3. Select _Delete_ on the target object
+4. A message should pop up to indicate success
+
+
+
+## Contact
+
+Joshua Görner - [jgoerner](https://www.linkedin.com/in/jgoerner/) - joshua.goerner[at]gmail.com
+
+
+
+## Acknowledgements
+* [O. Drew](https://github.com/othneildrew/Best-README-Template) - nice GH Readme template
+* [Hatchful](https://hatchful.shopify.com/) - Easy Logo Generation
\ No newline at end of file
diff --git a/aws/s3/build.gradle b/aws/s3/build.gradle
new file mode 100644
index 000000000..7d1b47fd1
--- /dev/null
+++ b/aws/s3/build.gradle
@@ -0,0 +1,41 @@
+plugins {
+ id 'org.springframework.boot' version '2.4.2'
+ id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+ id 'java'
+}
+
+group = 'io.jgoerner'
+version = '0.0.1-SNAPSHOT'
+sourceCompatibility = '11'
+
+repositories {
+ mavenCentral()
+}
+
+ext {
+ // set('springCloudVersion', "Hoxton.SR9")
+ set('springCloudVersion', "Finchley.SR1")
+}
+
+
+dependencies {
+ annotationProcessor 'org.projectlombok:lombok'
+ developmentOnly 'org.springframework.boot:spring-boot-devtools'
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
+ implementation 'org.springframework.cloud:spring-cloud-starter-aws'
+ implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+ implementation 'org.projectlombok:lombok:1.18.16'
+ runtimeOnly 'com.h2database:h2'
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+}
+
+dependencyManagement {
+ imports {
+ mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
+ }
+}
+
+test {
+ useJUnitPlatform()
+}
diff --git a/spring-cloud/sleuth-upstream-service/settings.gradle b/aws/s3/data/.gitkeep
similarity index 100%
rename from spring-cloud/sleuth-upstream-service/settings.gradle
rename to aws/s3/data/.gitkeep
diff --git a/aws/s3/gradle/wrapper/gradle-wrapper.jar b/aws/s3/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..e708b1c02
Binary files /dev/null and b/aws/s3/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/aws/s3/gradle/wrapper/gradle-wrapper.properties b/aws/s3/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..d5ad2ff8f
--- /dev/null
+++ b/aws/s3/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jan 04 22:38:53 CET 2021
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/aws/s3/gradlew b/aws/s3/gradlew
new file mode 100755
index 000000000..4f906e0c8
--- /dev/null
+++ b/aws/s3/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+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
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/aws/s3/gradlew.bat b/aws/s3/gradlew.bat
new file mode 100644
index 000000000..ac1b06f93
--- /dev/null
+++ b/aws/s3/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/aws/s3/images/logo.png b/aws/s3/images/logo.png
new file mode 100644
index 000000000..91f0001c8
Binary files /dev/null and b/aws/s3/images/logo.png differ
diff --git a/aws/s3/images/usage.gif b/aws/s3/images/usage.gif
new file mode 100644
index 000000000..5ba964699
Binary files /dev/null and b/aws/s3/images/usage.gif differ
diff --git a/aws/s3/settings.gradle b/aws/s3/settings.gradle
new file mode 100644
index 000000000..5b04ce064
--- /dev/null
+++ b/aws/s3/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 's3'
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/S3Application.java b/aws/s3/src/main/java/io/jgoerner/s3/S3Application.java
new file mode 100644
index 000000000..989fcc0b1
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/S3Application.java
@@ -0,0 +1,38 @@
+package io.jgoerner.s3;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
+import org.springframework.cloud.aws.autoconfigure.context.ContextInstanceDataAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.thymeleaf.templateresolver.FileTemplateResolver;
+import org.thymeleaf.templateresolver.ITemplateResolver;
+
+@SpringBootApplication(exclude = ContextInstanceDataAutoConfiguration.class)
+public class S3Application {
+
+ private final ThymeleafProperties properties;
+
+ @Value("${spring.thymeleaf.templates_root:}")
+ private String templatesRoot;
+
+ public S3Application(ThymeleafProperties properties) {
+ this.properties = properties;
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(S3Application.class, args);
+ }
+
+ @Bean
+ public ITemplateResolver defaultTemplateResolver() {
+ FileTemplateResolver resolver = new FileTemplateResolver();
+ resolver.setSuffix(properties.getSuffix());
+ resolver.setPrefix(templatesRoot);
+ resolver.setTemplateMode(properties.getMode());
+ resolver.setCacheable(properties.isCache());
+ return resolver;
+ }
+
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/adapter/in/RestApi.java b/aws/s3/src/main/java/io/jgoerner/s3/adapter/in/RestApi.java
new file mode 100644
index 000000000..1f7e9c8a8
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/adapter/in/RestApi.java
@@ -0,0 +1,128 @@
+package io.jgoerner.s3.adapter.in;
+
+import io.jgoerner.s3.application.port.in.object.*;
+import io.jgoerner.s3.application.port.in.space.*;
+import io.jgoerner.s3.domain.Object;
+import io.jgoerner.s3.domain.ObjectPartial;
+import io.jgoerner.s3.domain.Space;
+import io.jgoerner.s3.domain.SpacePartial;
+import lombok.SneakyThrows;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("api")
+@Log4j2
+public class RestApi {
+
+ private final CreateSpace spaceCreator;
+ private final GetAllSpaces allSpaceGetter;
+ private final RemoveSpace spaceRemover;
+ private final GetAllObjects allObjectsInSpaceGetter;
+ private final UploadObject objectUploader;
+ private final UpdateObject objectUpdater;
+ private final RemoveObject objectDeleter;
+ private final ForceRemoveSpace forceSpaceRemover;
+ private final CreateLink linkCreator;
+ private final SetTTL ttlUpdater;
+ private final RemoveTTL ttlRemover;
+
+ public RestApi(
+ CreateSpace spaceCreator,
+ GetAllSpaces allSpaceGetter,
+ RemoveSpace spaceRemover,
+ GetAllObjects allObjectsInSpaceGetter,
+ UploadObject objectUploader,
+ UpdateObject objectUpdater,
+ RemoveObject objectDeleter,
+ ForceRemoveSpace forceSpaceRemover,
+ CreateLink linkCreator,
+ SetTTL ttlUpdater,
+ RemoveTTL ttlRemover) {
+ this.spaceCreator = spaceCreator;
+ this.allSpaceGetter = allSpaceGetter;
+ this.spaceRemover = spaceRemover;
+ this.allObjectsInSpaceGetter = allObjectsInSpaceGetter;
+ this.objectUploader = objectUploader;
+ this.objectUpdater = objectUpdater;
+ this.objectDeleter = objectDeleter;
+ this.forceSpaceRemover = forceSpaceRemover;
+ this.linkCreator = linkCreator;
+ this.ttlUpdater = ttlUpdater;
+ this.ttlRemover = ttlRemover;
+ }
+
+ @GetMapping("/space")
+ List getSpaces() {
+ return allSpaceGetter.getAll();
+ }
+
+ @PostMapping("/space/{space}")
+ Space postSpace(@PathVariable String space) {
+ return spaceCreator.create(space);
+ }
+
+ @DeleteMapping("/space/{space}")
+ void deleteSpace(@PathVariable String space, @RequestParam Optional force) {
+ log.info("Got the value " + force);
+ force.ifPresentOrElse(
+ value -> {
+ if (value) {
+ forceSpaceRemover.forceRemove(space);
+ } else {
+ spaceRemover.remove(space);
+ }
+ },
+ () -> spaceRemover.remove(space));
+ }
+
+ @GetMapping("/space/{space}/object")
+ List getObjectsInSpace(@PathVariable String space) {
+ return allObjectsInSpaceGetter.getAllObjects(space);
+ }
+
+ @SneakyThrows
+ @PostMapping("/space/{space}/object")
+ Object postObject(
+ @PathVariable String space,
+ @RequestParam("file") MultipartFile file,
+ @RequestParam(required = false, name = "name") String name) {
+ var key = name != null ? name : file.getOriginalFilename();
+ return objectUploader.upload(space, key, file.getInputStream());
+ }
+
+ @PatchMapping("/space/{space}/object/{key}")
+ Object patchObject(
+ @PathVariable String space, @PathVariable String key, @RequestBody ObjectPartial body) {
+ log.info("Got the partial " + body);
+ return objectUpdater.update(space, key, body);
+ }
+
+ @DeleteMapping("/space/{space}/object/{key}")
+ void deleteObject(@PathVariable String space, @PathVariable String key) {
+ objectDeleter.delete(space, key);
+ }
+
+ @PostMapping("/space/{space}/object/{key}/url")
+ URL createLink(
+ @PathVariable String space,
+ @PathVariable String key,
+ @RequestParam(required = false, name = "duration", defaultValue = "300") Long duration) {
+ return linkCreator.createLink(space, key, duration);
+ }
+
+ @PatchMapping("/space/{space}")
+ void patchSpace(@PathVariable String space, @RequestBody(required = false) SpacePartial body) {
+ log.info("got " + body);
+ if (body.getTtlInDays() > 1) {
+ ttlUpdater.setTTL(space, body.getTtlInDays());
+ } else {
+ ttlRemover.removeTTL(space);
+ }
+ }
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/adapter/in/View.java b/aws/s3/src/main/java/io/jgoerner/s3/adapter/in/View.java
new file mode 100644
index 000000000..98cfedb44
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/adapter/in/View.java
@@ -0,0 +1,126 @@
+package io.jgoerner.s3.adapter.in;
+
+import io.jgoerner.s3.domain.ObjectPartial;
+import io.jgoerner.s3.domain.SpacePartial;
+import lombok.Data;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import java.util.Map;
+import java.util.Optional;
+
+@Log4j2
+@Controller()
+public class View {
+
+ private final RestApi api;
+
+ public View(RestApi api) {
+ this.api = api;
+ }
+
+ @GetMapping("space")
+ String space(Model model) {
+ var spaces = api.getSpaces();
+ model.addAttribute("spaces", spaces);
+ return "space-overview";
+ }
+
+ @GetMapping("space/{name}")
+ String spaceDetail(@PathVariable String name, Model model) {
+ model.addAllAttributes(Map.of("space", name, "objects", api.getObjectsInSpace(name)));
+ return "space-detail";
+ }
+
+ @GetMapping("space/{space}/make-public/{key}")
+ String makePublic(
+ @PathVariable String space, @PathVariable String key, RedirectAttributes redirectAttributes) {
+ api.patchObject(space, key, new ObjectPartial(true));
+ redirectAttributes.addFlashAttribute("success", "Made object public");
+ return "redirect:/space/" + space;
+ }
+
+ @GetMapping("space/{space}/make-private/{key}")
+ String makePrivate(
+ @PathVariable String space, @PathVariable String key, RedirectAttributes redirectAttributes) {
+ api.patchObject(space, key, new ObjectPartial(false));
+ redirectAttributes.addFlashAttribute("success", "Made object private");
+ return "redirect:/space/" + space;
+ }
+
+ @GetMapping("space/{space}/delete/{key}")
+ String deleteObject(@PathVariable String space, @PathVariable String key) {
+ api.deleteObject(space, key);
+ return "redirect:/space/" + space;
+ }
+
+ @GetMapping("/space/{space}/object-form")
+ String objectNew(@PathVariable String space, Model model) {
+ model.addAllAttributes(Map.of("space", space, "object", new ObjectForm()));
+ return "object-form";
+ }
+
+ @PostMapping("/space/{space}/object-form")
+ String postObject(
+ @PathVariable String space,
+ @RequestParam("file") MultipartFile file,
+ @ModelAttribute ObjectForm form,
+ RedirectAttributes redirectAttributes) {
+ api.postObject(space, file, form.getName());
+ redirectAttributes.addFlashAttribute("success", "Successfully uploaded " + form.getName());
+ return "redirect:/space/" + space;
+ }
+
+ @GetMapping("/space/{space}/magic/{key}")
+ String magicLink(
+ @PathVariable String space, @PathVariable String key, RedirectAttributes redirectAttributes) {
+ var link = api.createLink(space, key, 15L);
+ redirectAttributes.addFlashAttribute("warn", link);
+ return "redirect:/space/" + space;
+ }
+
+ @GetMapping("space-form")
+ String spaceNew(Model model) {
+ model.addAttribute("space", new SpaceForm());
+ return "space-form";
+ }
+
+ @PostMapping("space-form")
+ String newSpace(@ModelAttribute SpaceForm form, RedirectAttributes redirectAttributes) {
+ api.postSpace(form.getName());
+ redirectAttributes.addFlashAttribute("success", "Successfully created " + form.getName());
+ return "redirect:space";
+ }
+
+ @GetMapping("space/temporary/{name}")
+ String makeTemporary(@PathVariable String name) {
+ api.patchSpace(name, new SpacePartial(2));
+ return "redirect:/space";
+ }
+
+ @GetMapping("space/permanent/{name}")
+ String makePermanent(@PathVariable String name) {
+ api.patchSpace(name, new SpacePartial(-1));
+ return "redirect:/space";
+ }
+
+ @GetMapping("space/delete/{name}")
+ String delete(@PathVariable String name) {
+ api.deleteSpace(name, Optional.of(true));
+ return "redirect:/space";
+ }
+}
+
+@Data
+class SpaceForm {
+ String name;
+}
+
+@Data
+class ObjectForm {
+ String name;
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpaceEntity.java b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpaceEntity.java
new file mode 100644
index 000000000..5e1d319dd
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpaceEntity.java
@@ -0,0 +1,20 @@
+package io.jgoerner.s3.adapter.out.h2;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class SpaceEntity {
+ @Id private String name;
+ private String bucket;
+ private Integer ttl;
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpacePersistenceHandler.java b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpacePersistenceHandler.java
new file mode 100644
index 000000000..d9b36a0fa
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpacePersistenceHandler.java
@@ -0,0 +1,69 @@
+package io.jgoerner.s3.adapter.out.h2;
+
+import io.jgoerner.s3.application.port.out.space.*;
+import io.jgoerner.s3.domain.Space;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+@Log4j2
+public class SpacePersistenceHandler
+ implements CheckSpaceExistence,
+ SaveSpace,
+ RetrieveAllSpaces,
+ ResolveSpaceName,
+ DeleteSpace,
+ RetrieveSpaceByName {
+
+ private final SpaceRepository spaceRepository;
+
+ public SpacePersistenceHandler(SpaceRepository spaceRepository) {
+ this.spaceRepository = spaceRepository;
+ }
+
+ @Override
+ public boolean doesExist(String name) {
+ return this.spaceRepository.findById(name).isPresent();
+ }
+
+ @Override
+ public Space save(Space name) {
+ this.spaceRepository.save(SpacePersistenceHandler.mapPojoToJpa(name));
+ return name;
+ }
+
+ @Override
+ public List findAll() {
+ return spaceRepository.findAll().stream()
+ .map(SpacePersistenceHandler::mapJpaToPojo)
+ .collect(Collectors.toList());
+ }
+
+ private static SpaceEntity mapPojoToJpa(Space space) {
+ return new SpaceEntity(space.getName(), space.getBucket(), space.getTtl());
+ }
+
+ private static Space mapJpaToPojo(SpaceEntity entity) {
+ return new Space(entity.getName(), entity.getBucket(), entity.getTtl());
+ }
+
+ @Override
+ public String resolve(String name) {
+ var bucket = spaceRepository.findById(name).get().getBucket();
+ log.info("Space " + name + " was resolved to " + bucket);
+ return bucket;
+ }
+
+ @Override
+ public void delete(String name) {
+ spaceRepository.deleteById(name);
+ }
+
+ @Override
+ public Space retrieveByName(String name) {
+ return mapJpaToPojo(spaceRepository.findById(name).orElseThrow());
+ }
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpaceRepository.java b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpaceRepository.java
new file mode 100644
index 000000000..5c1b7b2b1
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/h2/SpaceRepository.java
@@ -0,0 +1,7 @@
+package io.jgoerner.s3.adapter.out.h2;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SpaceRepository extends JpaRepository {}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/s3/S3Repository.java b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/s3/S3Repository.java
new file mode 100644
index 000000000..3046e1123
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/adapter/out/s3/S3Repository.java
@@ -0,0 +1,143 @@
+package io.jgoerner.s3.adapter.out.s3;
+
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.*;
+import com.amazonaws.services.s3.model.lifecycle.LifecycleFilter;
+import com.amazonaws.waiters.WaiterParameters;
+import io.jgoerner.s3.application.port.out.bucket.*;
+import io.jgoerner.s3.application.port.out.object.*;
+import io.jgoerner.s3.domain.Object;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Repository;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Repository
+@Log4j2
+public class S3Repository
+ implements CreateBucket,
+ DeleteBucket,
+ ListObjects,
+ SaveObject,
+ MakeObjectPublic,
+ MakeObjectPrivate,
+ DeleteObject,
+ CreatePresignedUrl,
+ SetVisibilityInObjectLifecycle,
+ RemoveVisibilityInObjectLifecycle {
+
+ private final AmazonS3Client s3Client;
+
+ public S3Repository(AmazonS3Client s3Client) {
+ this.s3Client = s3Client;
+ }
+
+ @Override
+ public void create(String bucket) {
+ // send bucket creation request
+ s3Client.createBucket(bucket);
+ log.info("Request to create " + bucket + " sent");
+
+ // assure that bucket is available
+ s3Client.waiters().bucketExists().run(new WaiterParameters<>(new HeadBucketRequest(bucket)));
+ log.info("Bucket " + bucket + " is ready");
+ }
+
+ @Override
+ public void delete(String bucket) {
+ // send deletion request
+ s3Client.deleteBucket(bucket);
+ log.info("Request to delete " + bucket + " sent");
+
+ // assure bucket is deleted
+ s3Client.waiters().bucketNotExists().run(new WaiterParameters(new HeadBucketRequest(bucket)));
+ log.info("Bucket " + bucket + " is deleted");
+ }
+
+ @Override
+ public List listObjectsInBucket(String bucket) {
+ var items =
+ s3Client.listObjectsV2(bucket).getObjectSummaries().stream()
+ .parallel()
+ .map(S3ObjectSummary::getKey)
+ .map(key -> mapS3ToObject(bucket, key))
+ .collect(Collectors.toList());
+
+ log.info("Found " + items.size() + " objects in the bucket " + bucket);
+ return items;
+ }
+
+ @Override
+ public Object safe(String bucket, String key, String name, InputStream payload) {
+ var metadata = new ObjectMetadata();
+ metadata.addUserMetadata("name", name);
+ s3Client.putObject(bucket, key, payload, metadata);
+ log.info("Sent the request");
+ return Object.builder().name(name).key(key).url(s3Client.getUrl(bucket, key)).build();
+ }
+
+ @Override
+ public void makePublic(String bucket, String key) {
+ s3Client.setObjectAcl(bucket, key, CannedAccessControlList.PublicRead);
+ log.info("Sent request to make object in bucket " + bucket + " with key " + key + " public");
+ }
+
+ @Override
+ public void makePrivate(String bucket, String key) {
+ s3Client.setObjectAcl(bucket, key, CannedAccessControlList.BucketOwnerFullControl);
+ log.info("Sent request to make object in bucket " + bucket + " with key " + key + " private");
+ }
+
+ @Override
+ public void delete(String bucket, String key) {
+ s3Client.deleteObject(bucket, key);
+ log.info("Sent request to delete file with key " + key + " in bucket " + bucket);
+ }
+
+ @Override
+ public URL createURL(String bucket, String key, Long duration) {
+ var date = new Date(new Date().getTime() + duration * 1000); // 1 s * 1000 ms/s
+ var url = s3Client.generatePresignedUrl(bucket, key, date);
+ log.info("Generated the signature " + url);
+ return url;
+ }
+
+ @Override
+ public void setVisibility(String bucket, Integer ttlInDays) {
+ s3Client.setBucketLifecycleConfiguration(
+ bucket,
+ new BucketLifecycleConfiguration()
+ .withRules(
+ new BucketLifecycleConfiguration.Rule()
+ .withId("custom-expiration-id")
+ .withFilter(new LifecycleFilter())
+ .withStatus(BucketLifecycleConfiguration.ENABLED)
+ .withExpirationInDays(ttlInDays)));
+ }
+
+ @Override
+ public void removeVisibility(String bucket) {
+ s3Client.deleteBucketLifecycleConfiguration(bucket);
+ }
+
+ private Object mapS3ToObject(String bucket, String key) {
+
+ return Object.builder()
+ .name(s3Client.getObjectMetadata(bucket, key).getUserMetaDataOf("name"))
+ .key(key)
+ .url(s3Client.getUrl(bucket, key))
+ .isPublic(
+ s3Client.getObjectAcl(bucket, key).getGrantsAsList().stream()
+ .anyMatch(grant -> grant.equals(S3Repository.publicObjectReadGrant())))
+ .build();
+ }
+
+ private static Grant publicObjectReadGrant() {
+ return new Grant(
+ GroupGrantee.parseGroupGrantee(GroupGrantee.AllUsers.getIdentifier()), Permission.Read);
+ }
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/CreateLink.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/CreateLink.java
new file mode 100644
index 000000000..d6d3f187b
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/CreateLink.java
@@ -0,0 +1,7 @@
+package io.jgoerner.s3.application.port.in.object;
+
+import java.net.URL;
+
+public interface CreateLink {
+ URL createLink(String space, String key, Long duration);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/GetAllObjects.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/GetAllObjects.java
new file mode 100644
index 000000000..092512050
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/GetAllObjects.java
@@ -0,0 +1,9 @@
+package io.jgoerner.s3.application.port.in.object;
+
+import io.jgoerner.s3.domain.Object;
+
+import java.util.List;
+
+public interface GetAllObjects {
+ List getAllObjects(String space);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/RemoveObject.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/RemoveObject.java
new file mode 100644
index 000000000..cc06e487c
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/RemoveObject.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.in.object;
+
+public interface RemoveObject {
+ void delete(String space, String key);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/UpdateObject.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/UpdateObject.java
new file mode 100644
index 000000000..7f851064a
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/UpdateObject.java
@@ -0,0 +1,8 @@
+package io.jgoerner.s3.application.port.in.object;
+
+import io.jgoerner.s3.domain.ObjectPartial;
+import io.jgoerner.s3.domain.Object;
+
+public interface UpdateObject {
+ Object update(String space, String key, ObjectPartial updates);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/UploadObject.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/UploadObject.java
new file mode 100644
index 000000000..48ef3afcd
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/object/UploadObject.java
@@ -0,0 +1,9 @@
+package io.jgoerner.s3.application.port.in.object;
+
+import io.jgoerner.s3.domain.Object;
+
+import java.io.InputStream;
+
+public interface UploadObject {
+ Object upload(String space, String name, InputStream payload);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/CreateSpace.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/CreateSpace.java
new file mode 100644
index 000000000..928428a18
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/CreateSpace.java
@@ -0,0 +1,7 @@
+package io.jgoerner.s3.application.port.in.space;
+
+import io.jgoerner.s3.domain.Space;
+
+public interface CreateSpace {
+ Space create(String name);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/ForceRemoveSpace.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/ForceRemoveSpace.java
new file mode 100644
index 000000000..6e9142857
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/ForceRemoveSpace.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.in.space;
+
+public interface ForceRemoveSpace {
+ void forceRemove(String space);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/GetAllSpaces.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/GetAllSpaces.java
new file mode 100644
index 000000000..b56dbde67
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/GetAllSpaces.java
@@ -0,0 +1,9 @@
+package io.jgoerner.s3.application.port.in.space;
+
+import io.jgoerner.s3.domain.Space;
+
+import java.util.List;
+
+public interface GetAllSpaces {
+ List getAll();
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/RemoveSpace.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/RemoveSpace.java
new file mode 100644
index 000000000..84f4d3248
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/RemoveSpace.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.in.space;
+
+public interface RemoveSpace {
+ void remove(String space);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/RemoveTTL.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/RemoveTTL.java
new file mode 100644
index 000000000..4050512ff
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/RemoveTTL.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.in.space;
+
+public interface RemoveTTL {
+ void removeTTL(String space);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/SetTTL.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/SetTTL.java
new file mode 100644
index 000000000..0a029d297
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/in/space/SetTTL.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.in.space;
+
+public interface SetTTL {
+ void setTTL(String space, Integer ttlInDays);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/CreateBucket.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/CreateBucket.java
new file mode 100644
index 000000000..7a27aaeb2
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/CreateBucket.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.bucket;
+
+public interface CreateBucket {
+ void create(String bucket);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/DeleteBucket.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/DeleteBucket.java
new file mode 100644
index 000000000..d85393575
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/DeleteBucket.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.bucket;
+
+public interface DeleteBucket {
+ void delete(String bucket);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/ListObjects.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/ListObjects.java
new file mode 100644
index 000000000..092af4920
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/ListObjects.java
@@ -0,0 +1,9 @@
+package io.jgoerner.s3.application.port.out.bucket;
+
+import io.jgoerner.s3.domain.Object;
+
+import java.util.List;
+
+public interface ListObjects {
+ List listObjectsInBucket(String bucket);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/RemoveVisibilityInObjectLifecycle.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/RemoveVisibilityInObjectLifecycle.java
new file mode 100644
index 000000000..81e84ff79
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/RemoveVisibilityInObjectLifecycle.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.bucket;
+
+public interface RemoveVisibilityInObjectLifecycle {
+ void removeVisibility(String bucket);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/SetVisibilityInObjectLifecycle.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/SetVisibilityInObjectLifecycle.java
new file mode 100644
index 000000000..e59a490d8
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/bucket/SetVisibilityInObjectLifecycle.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.bucket;
+
+public interface SetVisibilityInObjectLifecycle {
+ void setVisibility(String bucket, Integer ttlInDays);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/CreatePresignedUrl.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/CreatePresignedUrl.java
new file mode 100644
index 000000000..3dfa7b51c
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/CreatePresignedUrl.java
@@ -0,0 +1,7 @@
+package io.jgoerner.s3.application.port.out.object;
+
+import java.net.URL;
+
+public interface CreatePresignedUrl {
+ URL createURL(String bucket, String key, Long duration);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/DeleteObject.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/DeleteObject.java
new file mode 100644
index 000000000..e953cbc93
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/DeleteObject.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.object;
+
+public interface DeleteObject {
+ void delete(String bucket, String key);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/MakeObjectPrivate.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/MakeObjectPrivate.java
new file mode 100644
index 000000000..d090222ff
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/MakeObjectPrivate.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.object;
+
+public interface MakeObjectPrivate {
+ void makePrivate(String bucket, String key);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/MakeObjectPublic.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/MakeObjectPublic.java
new file mode 100644
index 000000000..03aa838c5
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/MakeObjectPublic.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.object;
+
+public interface MakeObjectPublic {
+ void makePublic(String bucket, String key);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/SaveObject.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/SaveObject.java
new file mode 100644
index 000000000..96f70ad68
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/object/SaveObject.java
@@ -0,0 +1,9 @@
+package io.jgoerner.s3.application.port.out.object;
+
+import io.jgoerner.s3.domain.Object;
+
+import java.io.InputStream;
+
+public interface SaveObject {
+ Object safe(String bucket, String key, String name, InputStream payload);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/CheckSpaceExistence.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/CheckSpaceExistence.java
new file mode 100644
index 000000000..5865cdf96
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/CheckSpaceExistence.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.space;
+
+public interface CheckSpaceExistence {
+ boolean doesExist(String name);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/DeleteSpace.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/DeleteSpace.java
new file mode 100644
index 000000000..8f632e836
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/DeleteSpace.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.space;
+
+public interface DeleteSpace {
+ void delete(String name);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/ResolveSpaceName.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/ResolveSpaceName.java
new file mode 100644
index 000000000..89749a21f
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/ResolveSpaceName.java
@@ -0,0 +1,5 @@
+package io.jgoerner.s3.application.port.out.space;
+
+public interface ResolveSpaceName {
+ String resolve(String name);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/RetrieveAllSpaces.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/RetrieveAllSpaces.java
new file mode 100644
index 000000000..3ae461b57
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/RetrieveAllSpaces.java
@@ -0,0 +1,9 @@
+package io.jgoerner.s3.application.port.out.space;
+
+import io.jgoerner.s3.domain.Space;
+
+import java.util.List;
+
+public interface RetrieveAllSpaces {
+ List findAll();
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/RetrieveSpaceByName.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/RetrieveSpaceByName.java
new file mode 100644
index 000000000..b55b4392d
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/RetrieveSpaceByName.java
@@ -0,0 +1,7 @@
+package io.jgoerner.s3.application.port.out.space;
+
+import io.jgoerner.s3.domain.Space;
+
+public interface RetrieveSpaceByName {
+ Space retrieveByName(String name);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/SaveSpace.java b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/SaveSpace.java
new file mode 100644
index 000000000..046e6d05c
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/port/out/space/SaveSpace.java
@@ -0,0 +1,7 @@
+package io.jgoerner.s3.application.port.out.space;
+
+import io.jgoerner.s3.domain.Space;
+
+public interface SaveSpace {
+ Space save(Space name);
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/service/ObjectService.java b/aws/s3/src/main/java/io/jgoerner/s3/application/service/ObjectService.java
new file mode 100644
index 000000000..3c59311b7
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/service/ObjectService.java
@@ -0,0 +1,105 @@
+package io.jgoerner.s3.application.service;
+
+import io.jgoerner.s3.application.port.in.object.CreateLink;
+import io.jgoerner.s3.application.port.in.object.RemoveObject;
+import io.jgoerner.s3.application.port.in.object.UpdateObject;
+import io.jgoerner.s3.application.port.in.object.UploadObject;
+import io.jgoerner.s3.application.port.out.object.*;
+import io.jgoerner.s3.application.port.out.space.ResolveSpaceName;
+import io.jgoerner.s3.domain.Object;
+import io.jgoerner.s3.domain.ObjectPartial;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.UUID;
+
+@Service
+@Log4j2
+public class ObjectService implements UploadObject, UpdateObject, RemoveObject, CreateLink {
+
+ private final ResolveSpaceName bucketNameResolver;
+ private final SaveObject objectSaver;
+ private final MakeObjectPublic objectPublicMaker;
+ private final MakeObjectPrivate objectPrivateMaker;
+ private final DeleteObject objectDeleter;
+ private final CreatePresignedUrl presignedUrlCreator;
+
+ public ObjectService(
+ ResolveSpaceName bucketNameResolver,
+ SaveObject objectSaver,
+ MakeObjectPublic objectPublicMaker,
+ MakeObjectPrivate objectPrivateMaker,
+ DeleteObject objectDeleter,
+ CreatePresignedUrl presignedUrlCreator) {
+ this.bucketNameResolver = bucketNameResolver;
+ this.objectSaver = objectSaver;
+ this.objectPublicMaker = objectPublicMaker;
+ this.objectPrivateMaker = objectPrivateMaker;
+ this.objectDeleter = objectDeleter;
+ this.presignedUrlCreator = presignedUrlCreator;
+ }
+
+ @Override
+ public Object upload(String space, String name, InputStream payload) {
+ // check if bucket exists
+ var bucket = bucketNameResolver.resolve(space);
+
+ // generate a id & store in lookup table
+ var key = UUID.randomUUID().toString();
+
+ // save
+ log.info(
+ "Going to upload the file into to "
+ + bucket
+ + "/"
+ + key
+ + " with metadata name of "
+ + name);
+
+ return objectSaver.safe(bucket, key, name, payload);
+ }
+
+ @Override
+ public Object update(String space, String key, ObjectPartial updates) {
+ var bucket = bucketNameResolver.resolve(space);
+
+ if (updates.getIsPublic() != null) {
+ if (updates.getIsPublic()) {
+ log.info("going to open up to public");
+ objectPublicMaker.makePublic(bucket, key);
+ } else {
+ log.info("going to remove public access");
+ objectPrivateMaker.makePrivate(bucket, key);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void delete(String space, String key) {
+ var bucket = bucketNameResolver.resolve(space);
+
+ log.info("Going to delete the file with the key " + key + " in the bucket " + bucket);
+
+ objectDeleter.delete(bucket, key);
+ }
+
+ @Override
+ public URL createLink(String space, String key, Long duration) {
+ var bucket = bucketNameResolver.resolve(space);
+
+ log.info(
+ "Going to generate a link for the file "
+ + key
+ + " in bucket "
+ + bucket
+ + " with visibility duration of "
+ + duration
+ + " seconds");
+
+ return presignedUrlCreator.createURL(bucket, key, duration);
+ }
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/application/service/SpaceService.java b/aws/s3/src/main/java/io/jgoerner/s3/application/service/SpaceService.java
new file mode 100644
index 000000000..acdbecc18
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/application/service/SpaceService.java
@@ -0,0 +1,155 @@
+package io.jgoerner.s3.application.service;
+
+import io.jgoerner.s3.application.port.in.object.GetAllObjects;
+import io.jgoerner.s3.application.port.in.object.RemoveObject;
+import io.jgoerner.s3.application.port.in.space.*;
+import io.jgoerner.s3.application.port.out.bucket.*;
+import io.jgoerner.s3.application.port.out.object.DeleteObject;
+import io.jgoerner.s3.application.port.out.space.*;
+import io.jgoerner.s3.domain.Object;
+import io.jgoerner.s3.domain.Space;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.UUID;
+
+@Service
+@Log4j2
+public class SpaceService
+ implements CreateSpace,
+ GetAllSpaces,
+ GetAllObjects,
+ RemoveSpace,
+ ForceRemoveSpace,
+ SetTTL,
+ RemoveTTL {
+
+ private final CheckSpaceExistence spaceExistenceChecker;
+ private final SaveSpace spaceSaver;
+ private final RetrieveAllSpaces allSpaceRetriever;
+ private final CreateBucket bucketCreator;
+ private final ResolveSpaceName bucketNameResolver;
+ private final DeleteBucket bucketDeleter;
+ private final DeleteSpace spaceDeleter;
+ private final ListObjects objectLister;
+ private final DeleteObject objectDeleter;
+ private final SetVisibilityInObjectLifecycle objectLifecycleVisibilitySetter;
+ private final RemoveVisibilityInObjectLifecycle objectLifecycleVisibilityRemover;
+ private final RetrieveSpaceByName spaceByNameRetriever;
+
+ public SpaceService(
+ CheckSpaceExistence spaceExistenceChecker,
+ SaveSpace spaceSaver,
+ RetrieveAllSpaces allSpaceRetriever,
+ CreateBucket bucketCreator,
+ ResolveSpaceName bucketNameResolver,
+ DeleteBucket bucketDeleter,
+ DeleteSpace spaceDeleter,
+ ListObjects objectLister,
+ RemoveObject objectRemover,
+ DeleteObject objectDeleter,
+ SetVisibilityInObjectLifecycle objectLifecycleVisibilitySetter,
+ RemoveVisibilityInObjectLifecycle objectLifecycleVisibilityRemover,
+ RetrieveSpaceByName spaceByNameRetriever) {
+ this.spaceExistenceChecker = spaceExistenceChecker;
+ this.spaceSaver = spaceSaver;
+ this.allSpaceRetriever = allSpaceRetriever;
+ this.bucketNameResolver = bucketNameResolver;
+ this.bucketCreator = bucketCreator;
+ this.bucketDeleter = bucketDeleter;
+ this.spaceDeleter = spaceDeleter;
+ this.objectLister = objectLister;
+ this.objectDeleter = objectDeleter;
+ this.objectLifecycleVisibilitySetter = objectLifecycleVisibilitySetter;
+ this.objectLifecycleVisibilityRemover = objectLifecycleVisibilityRemover;
+ this.spaceByNameRetriever = spaceByNameRetriever;
+ }
+
+ @Override
+ public Space create(String name) {
+ // check if bucket exists
+ if (spaceExistenceChecker.doesExist(name)) {
+ log.info("Space " + name + " does already exist");
+ return null;
+ }
+
+ // create
+ Space space = new Space(name, "spring-boot-s3-tutorial-" + UUID.randomUUID().toString(), null);
+ log.info("Mapped space to bucket " + space);
+ bucketCreator.create(space.getBucket());
+
+ // create bucket meta
+ return this.spaceSaver.save(space);
+ }
+
+ @Override
+ public List getAll() {
+ var buckets = allSpaceRetriever.findAll();
+ log.info("Found " + buckets.size() + " buckets");
+ return buckets;
+ }
+
+ @Override
+ public List getAllObjects(String space) {
+ // get bucket from H2
+ var bucket = bucketNameResolver.resolve(space);
+
+ // return all files in bucket
+ return objectLister.listObjectsInBucket(bucket);
+ }
+
+ @Override
+ public void remove(String space) {
+ // get bucket from H2
+ var bucket = bucketNameResolver.resolve(space);
+
+ // delete from S3
+ bucketDeleter.delete(bucket);
+
+ // delete from H2
+ spaceDeleter.delete(space);
+ }
+
+ @Override
+ public void forceRemove(String space) {
+ // get bucket from H2
+ var bucket = bucketNameResolver.resolve(space);
+
+ // empty bucket
+ getAllObjects(space).stream()
+ .peek(log::info)
+ .forEach(object -> objectDeleter.delete(bucket, object.getKey()));
+
+ // get rid of bucket
+ remove(space);
+ }
+
+ @Override
+ public void setTTL(String space, Integer ttlInDays) {
+ var bucket = bucketNameResolver.resolve(space);
+ log.info("Going to adjust the TTL for the bucket " + bucket + " to " + ttlInDays + " day(s)");
+
+ // S3
+ objectLifecycleVisibilitySetter.setVisibility(bucket, ttlInDays);
+
+ // H2
+ var spaceEntity = spaceByNameRetriever.retrieveByName(space);
+ spaceEntity.setTtl(ttlInDays);
+ spaceSaver.save(spaceEntity);
+ }
+
+ @Override
+ public void removeTTL(String space) {
+ var bucket = bucketNameResolver.resolve(space);
+ log.info("Going to remove TTL policy for bucket " + bucket);
+
+ // S3
+ objectLifecycleVisibilityRemover.removeVisibility(bucket);
+
+ // H2
+ var spaceEntity = spaceByNameRetriever.retrieveByName(space);
+ spaceEntity.setTtl(null);
+ spaceSaver.save(spaceEntity);
+ }
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/domain/Object.java b/aws/s3/src/main/java/io/jgoerner/s3/domain/Object.java
new file mode 100644
index 000000000..9d88961fc
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/domain/Object.java
@@ -0,0 +1,15 @@
+package io.jgoerner.s3.domain;
+
+import lombok.Builder;
+import lombok.Value;
+
+import java.net.URL;
+
+@Value
+@Builder
+public class Object {
+ String name;
+ String key;
+ URL url;
+ @Builder.Default boolean isPublic = false;
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/domain/ObjectPartial.java b/aws/s3/src/main/java/io/jgoerner/s3/domain/ObjectPartial.java
new file mode 100644
index 000000000..81e7797bc
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/domain/ObjectPartial.java
@@ -0,0 +1,12 @@
+package io.jgoerner.s3.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class ObjectPartial {
+ Boolean isPublic;
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/domain/Space.java b/aws/s3/src/main/java/io/jgoerner/s3/domain/Space.java
new file mode 100644
index 000000000..60d644a58
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/domain/Space.java
@@ -0,0 +1,12 @@
+package io.jgoerner.s3.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@AllArgsConstructor
+@Data
+public class Space {
+ String name;
+ String bucket;
+ Integer ttl;
+}
diff --git a/aws/s3/src/main/java/io/jgoerner/s3/domain/SpacePartial.java b/aws/s3/src/main/java/io/jgoerner/s3/domain/SpacePartial.java
new file mode 100644
index 000000000..a0a6091c9
--- /dev/null
+++ b/aws/s3/src/main/java/io/jgoerner/s3/domain/SpacePartial.java
@@ -0,0 +1,12 @@
+package io.jgoerner.s3.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class SpacePartial {
+ Integer ttlInDays;
+}
diff --git a/aws/s3/src/main/resources/application.yaml b/aws/s3/src/main/resources/application.yaml
new file mode 100644
index 000000000..94b584e32
--- /dev/null
+++ b/aws/s3/src/main/resources/application.yaml
@@ -0,0 +1,36 @@
+spring:
+ datasource:
+ url: jdbc:h2:./data/metadb
+ username: admin
+ h2:
+ console:
+ enabled: true
+ path: /h2
+ jpa:
+ hibernate:
+ ddl-auto: update
+ # https://stackoverflow.com/questions/40057057/spring-boot-and-thymeleaf-hot-swap-templates-and-resources-once-again
+ thymeleaf:
+ cache: false
+ templates_root: src/main/resources/templates/
+ servlet:
+ multipart:
+ max-file-size: 15MB
+ max-request-size: 15MB
+
+cloud:
+ aws:
+ region:
+ static: eu-central-1
+ stack:
+ auto: false
+ credentials:
+ profile-name: dev
+
+# https://docs.spring.io/spring-cloud-aws/docs/2.2.3.RELEASE/reference/html/#amazon-sdk-configuration
+logging:
+ level:
+ com:
+ amazonaws:
+ util:
+ EC2MetadataUtils: error
diff --git a/aws/s3/src/main/resources/static/css/bulma.min.css b/aws/s3/src/main/resources/static/css/bulma.min.css
new file mode 100644
index 000000000..a807a314c
--- /dev/null
+++ b/aws/s3/src/main/resources/static/css/bulma.min.css
@@ -0,0 +1 @@
+/*! bulma.io v0.9.1 | MIT License | github.com/jgthms/bulma */@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.breadcrumb,.button,.delete,.file,.is-unselectable,.modal-close,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.highlight:not(:last-child),.level:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:290486px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{-webkit-animation:spinAround .5s infinite linear;animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.file-cta:active,.file-cta:focus,.file-name:active,.file-name:focus,.input:active,.input:focus,.is-active.button,.is-active.file-cta,.is-active.file-name,.is-active.input,.is-active.pagination-ellipsis,.is-active.pagination-link,.is-active.pagination-next,.is-active.pagination-previous,.is-active.textarea,.is-focused.button,.is-focused.file-cta,.is-focused.file-name,.is-focused.input,.is-focused.pagination-ellipsis,.is-focused.pagination-link,.is-focused.pagination-next,.is-focused.pagination-previous,.is-focused.textarea,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link:active,.pagination-link:focus,.pagination-next:active,.pagination-next:focus,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#3273dc;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #3273dc}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #3273dc}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#3273dc;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#3273dc;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#276cda;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#2366d1;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#3273dc;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3273dc}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;color:#3273dc}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#3273dc;border-color:#3273dc;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;box-shadow:none;color:#3273dc}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eef3fc;color:#2160c4}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e3ecfa;border-color:transparent;color:#2160c4}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#d8e4f8;border-color:transparent;color:#2160c4}.button.is-info{background-color:#3298dc;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#2793da;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#238cd1;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3298dc;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3298dc}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;color:#3298dc}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3298dc;border-color:#3298dc;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3298dc #3298dc!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;box-shadow:none;color:#3298dc}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3298dc #3298dc!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e3f1fa;border-color:transparent;color:#1d72aa}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#d8ebf8;border-color:transparent;color:#1d72aa}.button.is-success{background-color:#48c774;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec46d;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb67;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c774;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c774}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c774;color:#48c774}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c774;border-color:#48c774;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c774 #48c774!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c774;box-shadow:none;color:#48c774}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c774 #48c774!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf3;color:#257942}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ec;border-color:transparent;color:#257942}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e4;border-color:transparent;color:#257942}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffbeb;color:#947600}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff8de;border-color:transparent;color:#947600}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff6d1;border-color:transparent;color:#947600}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{border-radius:2px;font-size:.75rem}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em / 2));top:calc(50% - (1em / 2));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:290486px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){border-radius:2px;font-size:.75rem}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none!important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (max-width:1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width:1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width:1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:290486px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#3273dc;color:#fff}.notification.is-link.is-light{background-color:#eef3fc;color:#2160c4}.notification.is-info{background-color:#3298dc;color:#fff}.notification.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.notification.is-success{background-color:#48c774;color:#fff}.notification.is-success.is-light{background-color:#effaf3;color:#257942}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffbeb;color:#947600}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:290486px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#00d1b2 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#3273dc}.progress.is-link::-moz-progress-bar{background-color:#3273dc}.progress.is-link::-ms-fill{background-color:#3273dc}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#3273dc 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3298dc}.progress.is-info::-moz-progress-bar{background-color:#3298dc}.progress.is-info::-ms-fill{background-color:#3298dc}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3298dc 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c774}.progress.is-success::-moz-progress-bar{background-color:#48c774}.progress.is-success::-ms-fill{background-color:#48c774}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c774 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffdd57 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#3273dc;border-color:#3273dc;color:#fff}.table td.is-info,.table th.is-info{background-color:#3298dc;border-color:#3298dc;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c774;border-color:#48c774;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:inherit}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#3273dc;color:#fff}.tag:not(body).is-link.is-light{background-color:#eef3fc;color:#2160c4}.tag:not(body).is-info{background-color:#3298dc;color:#fff}.tag:not(body).is-info.is-light{background-color:#eef6fc;color:#1d72aa}.tag:not(body).is-success{background-color:#48c774;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf3;color:#257942}.tag:not(body).is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffbeb;color:#947600}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:290486px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title+.highlight{margin-top:-.75rem}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-weight:400;max-width:100%;overflow:hidden;padding:0}.highlight pre{overflow:auto;max-width:100%}.number{align-items:center;background-color:#f5f5f5;border-radius:290486px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered,.select select:hover,.textarea:hover{border-color:#b5b5b5}.input:active,.input:focus,.is-active.input,.is-active.textarea,.is-focused.input,.is-focused.textarea,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{border-color:#3273dc;box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:active,.is-white.input:focus,.is-white.is-active.input,.is-white.is-active.textarea,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.textarea:active,.is-white.textarea:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:active,.is-black.input:focus,.is-black.is-active.input,.is-black.is-active.textarea,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.textarea:active,.is-black.textarea:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:active,.is-light.input:focus,.is-light.is-active.input,.is-light.is-active.textarea,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.textarea:active,.is-light.textarea:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:active,.is-dark.input:focus,.is-dark.is-active.input,.is-dark.is-active.textarea,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.textarea:active,.is-dark.textarea:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:active,.is-primary.input:focus,.is-primary.is-active.input,.is-primary.is-active.textarea,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.textarea:active,.is-primary.textarea:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.input,.is-link.textarea{border-color:#3273dc}.is-link.input:active,.is-link.input:focus,.is-link.is-active.input,.is-link.is-active.textarea,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.textarea:active,.is-link.textarea:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.is-info.input,.is-info.textarea{border-color:#3298dc}.is-info.input:active,.is-info.input:focus,.is-info.is-active.input,.is-info.is-active.textarea,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.textarea:active,.is-info.textarea:focus{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.is-success.input,.is-success.textarea{border-color:#48c774}.is-success.input:active,.is-success.input:focus,.is-success.is-active.input,.is-success.is-active.textarea,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.textarea:active,.is-success.textarea:focus{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.is-warning.input,.is-warning.textarea{border-color:#ffdd57}.is-warning.input:active,.is-warning.input:focus,.is-warning.is-active.input,.is-warning.is-active.textarea,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.textarea:active,.is-warning.textarea:focus{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.is-danger.input,.is-danger.textarea{border-color:#f14668}.is-danger.input:active,.is-danger.input:focus,.is-danger.is-active.input,.is-danger.is-active.textarea,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.textarea:active,.is-danger.textarea:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:290486px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox input[disabled],.checkbox[disabled],.radio input[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#3273dc;right:1.125em;z-index:4}.select.is-rounded select{border-radius:290486px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#00b89c}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#3273dc}.select.is-link select{border-color:#3273dc}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#2366d1}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.select.is-info:not(:hover)::after{border-color:#3298dc}.select.is-info select{border-color:#3298dc}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#238cd1}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.select.is-success:not(:hover)::after{border-color:#48c774}.select.is-success select{border-color:#48c774}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb67}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd83d}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#3273dc;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#276cda;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,115,220,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#2366d1;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3298dc;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#2793da;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,152,220,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#238cd1;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c774;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec46d;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,116,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb67;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,221,87,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#3273dc}.help.is-info{color:#3298dc}.help.is-success{color:#48c774}.help.is-warning{color:#ffdd57}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#3273dc;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;max-width:100%;overflow:hidden;position:relative}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#3273dc;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#3273dc;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eef3fc}.message.is-link .message-header{background-color:#3273dc;color:#fff}.message.is-link .message-body{border-color:#3273dc;color:#2160c4}.message.is-info{background-color:#eef6fc}.message.is-info .message-header{background-color:#3298dc;color:#fff}.message.is-info .message-body{border-color:#3298dc;color:#1d72aa}.message.is-success{background-color:#effaf3}.message.is-success .message-header{background-color:#48c774;color:#fff}.message.is-success .message-body{border-color:#48c774;color:#257942}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#947600}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px){.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1024px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#3273dc;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#3273dc;color:#fff}}.navbar.is-info{background-color:#3298dc;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3298dc;color:#fff}}.navbar.is-success{background-color:#48c774;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c774;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#3273dc}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#3273dc}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#3273dc;border-bottom-style:solid;border-bottom-width:3px;color:#3273dc;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#3273dc;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1024px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#0a0a0a}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:290486px}.pagination.is-rounded .pagination-link{border-radius:290486px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#3273dc}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#3273dc;border-color:#3273dc;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#3273dc;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#3273dc}.panel.is-link .panel-block.is-active .panel-icon{color:#3273dc}.panel.is-info .panel-heading{background-color:#3298dc;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3298dc}.panel.is-info .panel-block.is-active .panel-icon{color:#3298dc}.panel.is-success .panel-heading{background-color:#48c774;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c774}.panel.is-success .panel-block.is-active .panel-icon{color:#48c774}.panel.is-warning .panel-heading{background-color:#ffdd57;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffdd57}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffdd57}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#3273dc}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#3273dc;color:#363636}.panel-block.is-active .panel-icon{color:#3273dc}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#3273dc;color:#3273dc}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#3273dc;border-color:#3273dc;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:290486px;border-top-left-radius:290486px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:290486px;border-top-right-radius:290486px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1023px){.column.is-narrow-touch{flex:none}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1024px){.column.is-narrow-desktop{flex:none}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1216px){.column.is-narrow-widescreen{flex:none}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1408px){.column.is-narrow-fullhd{flex:none}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable .column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1023px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1024px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1216px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1408px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1023px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1024px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1216px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1408px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1023px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1024px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1216px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1408px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#00d1b2!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#009e86!important}.has-background-primary{background-color:#00d1b2!important}.has-text-primary-light{color:#ebfffc!important}a.has-text-primary-light:focus,a.has-text-primary-light:hover{color:#b8fff4!important}.has-background-primary-light{background-color:#ebfffc!important}.has-text-primary-dark{color:#00947e!important}a.has-text-primary-dark:focus,a.has-text-primary-dark:hover{color:#00c7a9!important}.has-background-primary-dark{background-color:#00947e!important}.has-text-link{color:#3273dc!important}a.has-text-link:focus,a.has-text-link:hover{color:#205bbc!important}.has-background-link{background-color:#3273dc!important}.has-text-link-light{color:#eef3fc!important}a.has-text-link-light:focus,a.has-text-link-light:hover{color:#c2d5f5!important}.has-background-link-light{background-color:#eef3fc!important}.has-text-link-dark{color:#2160c4!important}a.has-text-link-dark:focus,a.has-text-link-dark:hover{color:#3b79de!important}.has-background-link-dark{background-color:#2160c4!important}.has-text-info{color:#3298dc!important}a.has-text-info:focus,a.has-text-info:hover{color:#207dbc!important}.has-background-info{background-color:#3298dc!important}.has-text-info-light{color:#eef6fc!important}a.has-text-info-light:focus,a.has-text-info-light:hover{color:#c2e0f5!important}.has-background-info-light{background-color:#eef6fc!important}.has-text-info-dark{color:#1d72aa!important}a.has-text-info-dark:focus,a.has-text-info-dark:hover{color:#248fd6!important}.has-background-info-dark{background-color:#1d72aa!important}.has-text-success{color:#48c774!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a85c!important}.has-background-success{background-color:#48c774!important}.has-text-success-light{color:#effaf3!important}a.has-text-success-light:focus,a.has-text-success-light:hover{color:#c8eed6!important}.has-background-success-light{background-color:#effaf3!important}.has-text-success-dark{color:#257942!important}a.has-text-success-dark:focus,a.has-text-success-dark:hover{color:#31a058!important}.has-background-success-dark{background-color:#257942!important}.has-text-warning{color:#ffdd57!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd324!important}.has-background-warning{background-color:#ffdd57!important}.has-text-warning-light{color:#fffbeb!important}a.has-text-warning-light:focus,a.has-text-warning-light:hover{color:#fff1b8!important}.has-background-warning-light{background-color:#fffbeb!important}.has-text-warning-dark{color:#947600!important}a.has-text-warning-dark:focus,a.has-text-warning-dark:hover{color:#c79f00!important}.has-background-warning-dark{background-color:#947600!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-danger-light{color:#feecf0!important}a.has-text-danger-light:focus,a.has-text-danger-light:hover{color:#fabdc9!important}.has-background-danger-light{background-color:#feecf0!important}.has-text-danger-dark{color:#cc0f35!important}a.has-text-danger-dark:focus,a.has-text-danger-dark:hover{color:#ee2049!important}.has-background-danger-dark{background-color:#cc0f35!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.is-flex-direction-row{flex-direction:row!important}.is-flex-direction-row-reverse{flex-direction:row-reverse!important}.is-flex-direction-column{flex-direction:column!important}.is-flex-direction-column-reverse{flex-direction:column-reverse!important}.is-flex-wrap-nowrap{flex-wrap:nowrap!important}.is-flex-wrap-wrap{flex-wrap:wrap!important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse!important}.is-justify-content-flex-start{justify-content:flex-start!important}.is-justify-content-flex-end{justify-content:flex-end!important}.is-justify-content-center{justify-content:center!important}.is-justify-content-space-between{justify-content:space-between!important}.is-justify-content-space-around{justify-content:space-around!important}.is-justify-content-space-evenly{justify-content:space-evenly!important}.is-justify-content-start{justify-content:start!important}.is-justify-content-end{justify-content:end!important}.is-justify-content-left{justify-content:left!important}.is-justify-content-right{justify-content:right!important}.is-align-content-flex-start{align-content:flex-start!important}.is-align-content-flex-end{align-content:flex-end!important}.is-align-content-center{align-content:center!important}.is-align-content-space-between{align-content:space-between!important}.is-align-content-space-around{align-content:space-around!important}.is-align-content-space-evenly{align-content:space-evenly!important}.is-align-content-stretch{align-content:stretch!important}.is-align-content-start{align-content:start!important}.is-align-content-end{align-content:end!important}.is-align-content-baseline{align-content:baseline!important}.is-align-items-stretch{align-items:stretch!important}.is-align-items-flex-start{align-items:flex-start!important}.is-align-items-flex-end{align-items:flex-end!important}.is-align-items-center{align-items:center!important}.is-align-items-baseline{align-items:baseline!important}.is-align-items-start{align-items:start!important}.is-align-items-end{align-items:end!important}.is-align-items-self-start{align-items:self-start!important}.is-align-items-self-end{align-items:self-end!important}.is-align-self-auto{align-self:auto!important}.is-align-self-flex-start{align-self:flex-start!important}.is-align-self-flex-end{align-self:flex-end!important}.is-align-self-center{align-self:center!important}.is-align-self-baseline{align-self:baseline!important}.is-align-self-stretch{align-self:stretch!important}.is-flex-grow-0{flex-grow:0!important}.is-flex-grow-1{flex-grow:1!important}.is-flex-grow-2{flex-grow:2!important}.is-flex-grow-3{flex-grow:3!important}.is-flex-grow-4{flex-grow:4!important}.is-flex-grow-5{flex-grow:5!important}.is-flex-shrink-0{flex-shrink:0!important}.is-flex-shrink-1{flex-shrink:1!important}.is-flex-shrink-2{flex-shrink:2!important}.is-flex-shrink-3{flex-shrink:3!important}.is-flex-shrink-4{flex-shrink:4!important}.is-flex-shrink-5{flex-shrink:5!important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-clickable{cursor:pointer!important}.is-clipped{overflow:hidden!important}.is-relative{position:relative!important}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.m-0{margin:0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.25rem!important}.mt-1{margin-top:.25rem!important}.mr-1{margin-right:.25rem!important}.mb-1{margin-bottom:.25rem!important}.ml-1{margin-left:.25rem!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.m-2{margin:.5rem!important}.mt-2{margin-top:.5rem!important}.mr-2{margin-right:.5rem!important}.mb-2{margin-bottom:.5rem!important}.ml-2{margin-left:.5rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.m-3{margin:.75rem!important}.mt-3{margin-top:.75rem!important}.mr-3{margin-right:.75rem!important}.mb-3{margin-bottom:.75rem!important}.ml-3{margin-left:.75rem!important}.mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.m-4{margin:1rem!important}.mt-4{margin-top:1rem!important}.mr-4{margin-right:1rem!important}.mb-4{margin-bottom:1rem!important}.ml-4{margin-left:1rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.m-5{margin:1.5rem!important}.mt-5{margin-top:1.5rem!important}.mr-5{margin-right:1.5rem!important}.mb-5{margin-bottom:1.5rem!important}.ml-5{margin-left:1.5rem!important}.mx-5{margin-left:1.5rem!important;margin-right:1.5rem!important}.my-5{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-6{margin:3rem!important}.mt-6{margin-top:3rem!important}.mr-6{margin-right:3rem!important}.mb-6{margin-bottom:3rem!important}.ml-6{margin-left:3rem!important}.mx-6{margin-left:3rem!important;margin-right:3rem!important}.my-6{margin-top:3rem!important;margin-bottom:3rem!important}.p-0{padding:0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.25rem!important}.pt-1{padding-top:.25rem!important}.pr-1{padding-right:.25rem!important}.pb-1{padding-bottom:.25rem!important}.pl-1{padding-left:.25rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.p-2{padding:.5rem!important}.pt-2{padding-top:.5rem!important}.pr-2{padding-right:.5rem!important}.pb-2{padding-bottom:.5rem!important}.pl-2{padding-left:.5rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.p-3{padding:.75rem!important}.pt-3{padding-top:.75rem!important}.pr-3{padding-right:.75rem!important}.pb-3{padding-bottom:.75rem!important}.pl-3{padding-left:.75rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.p-4{padding:1rem!important}.pt-4{padding-top:1rem!important}.pr-4{padding-right:1rem!important}.pb-4{padding-bottom:1rem!important}.pl-4{padding-left:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.p-5{padding:1.5rem!important}.pt-5{padding-top:1.5rem!important}.pr-5{padding-right:1.5rem!important}.pb-5{padding-bottom:1.5rem!important}.pl-5{padding-left:1.5rem!important}.px-5{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-5{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-6{padding:3rem!important}.pt-6{padding-top:3rem!important}.pr-6{padding-right:3rem!important}.pb-6{padding-bottom:3rem!important}.pl-6{padding-left:3rem!important}.px-6{padding-left:3rem!important;padding-right:3rem!important}.py-6{padding-top:3rem!important;padding-bottom:3rem!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6{font-size:1rem!important}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1023px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1024px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1216px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1408px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1023px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1024px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1216px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1408px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1023px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1024px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1216px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1408px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1023px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1024px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1216px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1408px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1023px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1024px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1216px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1408px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.is-italic{font-style:italic!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:monospace!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1023px){.is-block-touch{display:block!important}}@media screen and (min-width:1024px){.is-block-desktop{display:block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1216px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1408px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1023px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1024px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1216px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1408px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1023px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1024px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1216px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1408px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1023px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1024px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1216px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1408px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1023px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1024px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1216px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1408px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1023px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1024px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1216px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1408px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1023px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1024px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1216px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1408px){.is-invisible-fullhd{visibility:hidden!important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}}.hero.is-link{background-color:#3273dc;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-link .navbar-menu{background-color:#3273dc}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#2366d1;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3273dc}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#1577c6 0,#3273dc 71%,#4366e5 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1577c6 0,#3273dc 71%,#4366e5 100%)}}.hero.is-info{background-color:#3298dc;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-info .navbar-menu{background-color:#3298dc}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#238cd1;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3298dc}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#159dc6 0,#3298dc 71%,#4389e5 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#159dc6 0,#3298dc 71%,#4389e5 100%)}}.hero.is-success{background-color:#48c774;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-success .navbar-menu{background-color:#48c774}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb67;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c774}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b342 0,#48c774 71%,#56d296 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b342 0,#48c774 71%,#56d296 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffaf24 0,#ffdd57 71%,#fffa70 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffaf24 0,#ffdd57 71%,#fffa70 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding:9rem 1.5rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding:18rem 1.5rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}.section{padding:3rem 1.5rem}@media screen and (min-width:1024px){.section.is-medium{padding:9rem 1.5rem}.section.is-large{padding:18rem 1.5rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}
\ No newline at end of file
diff --git a/aws/s3/src/main/resources/templates/fragments/head.html b/aws/s3/src/main/resources/templates/fragments/head.html
new file mode 100644
index 000000000..e429301ee
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/fragments/head.html
@@ -0,0 +1,7 @@
+
+
+
+ File Share
+
+
+
\ No newline at end of file
diff --git a/aws/s3/src/main/resources/templates/fragments/navbar.html b/aws/s3/src/main/resources/templates/fragments/navbar.html
new file mode 100644
index 000000000..93157ea36
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/fragments/navbar.html
@@ -0,0 +1,43 @@
+
+
+
\ No newline at end of file
diff --git a/aws/s3/src/main/resources/templates/fragments/notification.html b/aws/s3/src/main/resources/templates/fragments/notification.html
new file mode 100644
index 000000000..8c7b4ea22
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/fragments/notification.html
@@ -0,0 +1,23 @@
+
+
+
diff --git a/aws/s3/src/main/resources/templates/index.html b/aws/s3/src/main/resources/templates/index.html
new file mode 100644
index 000000000..3cf251b85
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+ Private File Share
+
+
+ powered by Spring Boot & Simple Storage Service (S3)
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws/s3/src/main/resources/templates/object-form.html b/aws/s3/src/main/resources/templates/object-form.html
new file mode 100644
index 000000000..5bf62e756
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/object-form.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/aws/s3/src/main/resources/templates/space-detail.html b/aws/s3/src/main/resources/templates/space-detail.html
new file mode 100644
index 000000000..bd353b1a7
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/space-detail.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aws/s3/src/main/resources/templates/space-form.html b/aws/s3/src/main/resources/templates/space-form.html
new file mode 100644
index 000000000..1aec742ec
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/space-form.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/aws/s3/src/main/resources/templates/space-overview.html b/aws/s3/src/main/resources/templates/space-overview.html
new file mode 100644
index 000000000..376946c9b
--- /dev/null
+++ b/aws/s3/src/main/resources/templates/space-overview.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aws/s3/src/test/java/io/jgoerner/s3/S3ApplicationTests.java b/aws/s3/src/test/java/io/jgoerner/s3/S3ApplicationTests.java
new file mode 100644
index 000000000..b2e3a7cb2
--- /dev/null
+++ b/aws/s3/src/test/java/io/jgoerner/s3/S3ApplicationTests.java
@@ -0,0 +1,13 @@
+package io.jgoerner.s3;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class S3ApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
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-caching-redis/Dockerfile b/aws/spring-cloud-caching-redis/Dockerfile
new file mode 100644
index 000000000..f5b6f2314
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/Dockerfile
@@ -0,0 +1,5 @@
+FROM --platform=linux/amd64 adoptopenjdk/openjdk11:jre-11.0.10_9-alpine
+ARG JAR_FILE=build/libs/*.jar
+COPY ${JAR_FILE} service.jar
+EXPOSE 8080
+ENTRYPOINT [ "sh", "-c", "java -jar -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 service.jar" ]
diff --git a/aws/spring-cloud-caching-redis/README.md b/aws/spring-cloud-caching-redis/README.md
new file mode 100644
index 000000000..f69b0d695
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/README.md
@@ -0,0 +1,4 @@
+# Caching In Spring Boot Application with ElastiCache for Redis
+
+* This example showcases how you can configure spring cache and connect it with
+AWS ElastiCache
diff --git a/aws/spring-cloud-caching-redis/build.gradle b/aws/spring-cloud-caching-redis/build.gradle
new file mode 100644
index 000000000..b46d9a0cc
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id 'org.springframework.boot' version '2.4.5'
+ id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+ id 'java'
+}
+
+group = 'io.reflectoring'
+version = '0.0.1-SNAPSHOT'
+sourceCompatibility = '11'
+
+configurations {
+ compileOnly {
+ extendsFrom annotationProcessor
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-cache'
+ implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+ implementation 'io.awspring.cloud:spring-cloud-starter-aws'
+// implementation 'com.amazonaws:aws-java-sdk-cloudformation'
+ implementation 'com.amazonaws:aws-java-sdk-elasticache'
+ compileOnly 'org.projectlombok:lombok'
+ runtimeOnly 'com.h2database:h2'
+ annotationProcessor 'org.projectlombok:lombok'
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+}
+
+dependencyManagement {
+ imports {
+ mavenBom 'io.awspring.cloud:spring-cloud-aws-dependencies:2.3.1'
+ }
+}
+
+test {
+ useJUnitPlatform()
+}
diff --git a/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.jar b/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..e708b1c02
Binary files /dev/null and b/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.properties b/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..442d9132e
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/aws/spring-cloud-caching-redis/gradlew b/aws/spring-cloud-caching-redis/gradlew
new file mode 100755
index 000000000..4f906e0c8
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+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
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/aws/spring-cloud-caching-redis/gradlew.bat b/aws/spring-cloud-caching-redis/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/aws/spring-cloud-caching-redis/settings.gradle b/aws/spring-cloud-caching-redis/settings.gradle
new file mode 100644
index 000000000..43a95ed0b
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'spring-cloud-redis'
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/SpringCloudRedisApplication.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/SpringCloudRedisApplication.java
new file mode 100644
index 000000000..19757810f
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/SpringCloudRedisApplication.java
@@ -0,0 +1,12 @@
+package io.reflectoring.springcloudredis;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringCloudRedisApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringCloudRedisApplication.class, args);
+ }
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/configuration/EnableCache.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/configuration/EnableCache.java
new file mode 100644
index 000000000..5655b53e2
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/configuration/EnableCache.java
@@ -0,0 +1,10 @@
+package io.reflectoring.springcloudredis.configuration;
+
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableCaching
+public class EnableCache {
+
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/controller/ProductController.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/controller/ProductController.java
new file mode 100644
index 000000000..e08f41b1c
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/controller/ProductController.java
@@ -0,0 +1,41 @@
+package io.reflectoring.springcloudredis.controller;
+
+import io.reflectoring.springcloudredis.entity.Product;
+import io.reflectoring.springcloudredis.model.ProductInput;
+import io.reflectoring.springcloudredis.service.ProductService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@Slf4j(topic = "PRODUCT_CONTROLLER")
+@RestController
+@RequestMapping("/product")
+@AllArgsConstructor
+public class ProductController {
+ private final ProductService productService;
+
+ @GetMapping("/{id}")
+ public Product getProduct(@PathVariable String id){
+ return productService.getProduct(id);
+ }
+
+ @DeleteMapping("/{id}")
+ public void deleteProduct(@PathVariable String id){
+ productService.deleteProduct(id);
+ }
+
+ @PutMapping("/{id}")
+ public Product updateProduct(@PathVariable String id, @RequestBody @Valid ProductInput input){
+ return productService.updateProduct(id, input);
+ }
+
+ @PostMapping
+ public Product addProduct(@RequestBody @Valid ProductInput input){
+ return productService.addProduct(input);
+ }
+
+
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/controller/UserController.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/controller/UserController.java
new file mode 100644
index 000000000..9f061c279
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/controller/UserController.java
@@ -0,0 +1,24 @@
+package io.reflectoring.springcloudredis.controller;
+
+import io.reflectoring.springcloudredis.entity.User;
+import io.reflectoring.springcloudredis.service.UserService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j(topic = "PRODUCT_CONTROLLER")
+@RestController
+@RequestMapping("/user")
+@AllArgsConstructor
+public class UserController {
+
+ private final UserService userService;
+
+ @GetMapping("/{id}")
+ public User getUser(@PathVariable String id) {
+ return userService.getUser(id);
+ }
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/Category.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/Category.java
new file mode 100644
index 000000000..fd1fd4999
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/Category.java
@@ -0,0 +1,12 @@
+package io.reflectoring.springcloudredis.entity;
+
+import java.io.Serializable;
+
+public enum Category implements Serializable {
+ MOBILE,
+ TV_APPLIANCES,
+ MEN_FASHION,
+ WOMEN_FASHION,
+ BOOKS,
+ BEAUTY
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/Product.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/Product.java
new file mode 100644
index 000000000..b62060a92
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/Product.java
@@ -0,0 +1,44 @@
+package io.reflectoring.springcloudredis.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Entity
+@Getter
+@Setter
+public class Product implements Serializable {
+ @Id
+ private String id;
+
+ private String name;
+
+ private Double price;
+
+ private LocalDateTime manufacturingDate;
+
+ @Transient
+ private Object data;
+
+ private Double weight;
+
+ @Embedded
+ private Dimension dimension;
+
+ @Enumerated(EnumType.STRING)
+ private Category category;
+
+ @Embeddable
+ @Getter
+ @Setter
+ public static class Dimension implements Serializable {
+ private Double height;
+
+ private Double width;
+ }
+
+
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/User.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/User.java
new file mode 100644
index 000000000..38ecef33b
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/entity/User.java
@@ -0,0 +1,20 @@
+package io.reflectoring.springcloudredis.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import java.io.Serializable;
+
+@Entity
+@Getter
+@Setter
+public class User implements Serializable {
+ @Id
+ private String id;
+
+ private String firstName;
+
+ private String lastName;
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/model/ProductInput.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/model/ProductInput.java
new file mode 100644
index 000000000..718e68037
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/model/ProductInput.java
@@ -0,0 +1,23 @@
+package io.reflectoring.springcloudredis.model;
+
+import io.reflectoring.springcloudredis.entity.Category;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Getter
+@Setter
+public class ProductInput {
+
+ @NotBlank
+ private String name;
+
+ @NotNull
+ private Double price;
+
+ private Double weight;
+
+ private Category category;
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/repository/ProductRepository.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/repository/ProductRepository.java
new file mode 100644
index 000000000..71208317e
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/repository/ProductRepository.java
@@ -0,0 +1,9 @@
+package io.reflectoring.springcloudredis.repository;
+
+import io.reflectoring.springcloudredis.entity.Product;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ProductRepository extends JpaRepository {
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/repository/UserRepository.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/repository/UserRepository.java
new file mode 100644
index 000000000..ee78a1f24
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/repository/UserRepository.java
@@ -0,0 +1,9 @@
+package io.reflectoring.springcloudredis.repository;
+
+import io.reflectoring.springcloudredis.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/service/ProductService.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/service/ProductService.java
new file mode 100644
index 000000000..e5ded288d
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/service/ProductService.java
@@ -0,0 +1,52 @@
+package io.reflectoring.springcloudredis.service;
+
+import io.reflectoring.springcloudredis.entity.Category;
+import io.reflectoring.springcloudredis.entity.Product;
+import io.reflectoring.springcloudredis.model.ProductInput;
+import io.reflectoring.springcloudredis.repository.ProductRepository;
+import lombok.AllArgsConstructor;
+import org.springframework.cache.annotation.CacheConfig;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.util.Objects;
+
+@Service
+@AllArgsConstructor
+@CacheConfig(cacheNames = "product-cache")
+public class ProductService {
+ private final ProductRepository repository;
+
+ @Cacheable
+ public Product getProduct(String id) {
+ return repository.findById(id).orElseThrow(()->
+ new RuntimeException("No such product found with id"));
+ }
+
+ public Product addProduct(ProductInput productInput){
+ var product = new Product();
+ product.setName(productInput.getName());
+ product.setPrice(productInput.getPrice());
+ product.setWeight(product.getWeight());
+ product.setCategory(Objects.isNull(productInput.getCategory())? Category.BOOKS: productInput.getCategory());
+ return repository.save(product);
+ }
+
+ @CacheEvict
+ public void deleteProduct(String id) {
+ repository.deleteById(id);
+ }
+
+ @CachePut(key = "#id")
+ public Product updateProduct(String id, ProductInput productInput) {
+ var product = new Product();
+ product.setId(id);
+ product.setName(productInput.getName());
+ product.setPrice(productInput.getPrice());
+ product.setWeight(product.getWeight());
+ product.setCategory(Objects.isNull(productInput.getCategory())? Category.BOOKS: productInput.getCategory());
+ return repository.save(product);
+ }
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/service/UserService.java b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/service/UserService.java
new file mode 100644
index 000000000..0a6f64e43
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/java/io/reflectoring/springcloudredis/service/UserService.java
@@ -0,0 +1,22 @@
+package io.reflectoring.springcloudredis.service;
+
+import io.reflectoring.springcloudredis.entity.User;
+import io.reflectoring.springcloudredis.repository.UserRepository;
+import lombok.AllArgsConstructor;
+import org.springframework.cache.annotation.CacheConfig;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+@CacheConfig(cacheNames = "product-cache")
+public class UserService {
+
+ private final UserRepository repository;
+
+ @Cacheable
+ public User getUser(String id){
+ return repository.findById(id).orElseThrow(()->
+ new RuntimeException("No such user found with id"));
+ }
+}
diff --git a/aws/spring-cloud-caching-redis/src/main/resources/application.yaml b/aws/spring-cloud-caching-redis/src/main/resources/application.yaml
new file mode 100644
index 000000000..ac41674d7
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/resources/application.yaml
@@ -0,0 +1,24 @@
+spring:
+ datasource:
+ initialization-mode: always
+ application:
+ name: spring-cloud-redis
+ jpa:
+ show-sql: true
+ generate-ddl: true
+ hibernate:
+ ddl-auto: create-drop
+#cloud:
+# aws:
+# stack:
+# name: spring-cache-2
+cloud:
+ aws:
+ elasticache:
+ clusters:
+ -
+ name: product-cache
+ expiration: 100
+ -
+ name: user-cache
+ expiration: 6000
diff --git a/aws/spring-cloud-caching-redis/src/main/resources/data.sql b/aws/spring-cloud-caching-redis/src/main/resources/data.sql
new file mode 100644
index 000000000..eb6f4b5bc
--- /dev/null
+++ b/aws/spring-cloud-caching-redis/src/main/resources/data.sql
@@ -0,0 +1,39 @@
+insert into product
+(id, name, price, manufacturing_date, weight, height, width, category)
+values
+('prod_1', 'one plus 8T', 100, now(), 2.2, 3.4, 5.4, 'MOBILE');
+
+insert into product
+(id, name, price, manufacturing_date, weight, height, width, category)
+values
+('prod_2', 'Samsung 100 S', 200, now(), 5.6, 7.9, 4.3, 'MOBILE');
+
+insert into product
+(id, name, price, manufacturing_date, weight, height, width, category)
+values
+('prod_3', 'Armani jacket size 32', 1100, now(), 1.3, 10.4, 29.5, 'MEN_FASHION');
+
+insert into product
+(id, name, price, manufacturing_date, weight, height, width, category)
+values
+('prod_4', 'Zara purse', 500, now(), 3.7, 50.6, 70.6, 'WOMEN_FASHION');
+
+insert into product
+(id, name, price, manufacturing_date, weight, height, width, category)
+values
+('prod_5', 'Sony Bravia ', 2000, now(), 25.5, 48.8, 25.9, 'TV_APPLIANCES');
+
+insert into product
+(id, name, price, manufacturing_date, weight, height, width, category)
+values
+('prod_6', 'zara jacket green color', 1500, now(), 1.3, 10.4, 29.5, 'MEN_FASHION');
+
+insert into user
+(id, first_name, last_name)
+values
+('user_1', 'john', 'doe');
+
+insert into user
+(id, first_name, last_name)
+values
+('user_2', 'richard', 'mayor');
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/springcloudrds/.mvn/wrapper/MavenWrapperDownloader.java b/aws/springcloudrds/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/springcloudrds/.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/aws/springcloudrds/.mvn/wrapper/maven-wrapper.jar b/aws/springcloudrds/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/springcloudrds/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/springcloudrds/.mvn/wrapper/maven-wrapper.properties b/aws/springcloudrds/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/springcloudrds/.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/aws/springcloudrds/README.md b/aws/springcloudrds/README.md
new file mode 100644
index 000000000..61b95a5dd
--- /dev/null
+++ b/aws/springcloudrds/README.md
@@ -0,0 +1,10 @@
+# Working with AWS RDS and Spring Cloud
+
+Example code for using Spring Cloud AWS JDBC
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Working with AWS RDS and Spring Cloud](https://reflectoring.io/spring-cloud-aws-rds/)
+
diff --git a/aws/springcloudrds/mvnw b/aws/springcloudrds/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/springcloudrds/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/aws/springcloudrds/mvnw.cmd b/aws/springcloudrds/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/springcloudrds/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/aws/springcloudrds/pom.xml b/aws/springcloudrds/pom.xml
new file mode 100644
index 000000000..c40df3f14
--- /dev/null
+++ b/aws/springcloudrds/pom.xml
@@ -0,0 +1,89 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.5
+
+
+ io.pratik
+ springcloudrds
+ 0.0.1-SNAPSHOT
+ springcloudrds
+ Demo project for Spring Cloud RDS
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ io.awspring.cloud
+ spring-cloud-starter-aws-jdbc
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ org.aspectj
+ aspectjrt
+
+
+ org.aspectj
+ aspectjweaver
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ 2.3.0
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
+
diff --git a/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/ApplicationConfiguration.java b/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/ApplicationConfiguration.java
new file mode 100644
index 000000000..ade9eb530
--- /dev/null
+++ b/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/ApplicationConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ *
+ */
+package io.pratik.springcloudrds;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import io.awspring.cloud.jdbc.config.annotation.RdsInstanceConfigurer;
+import io.awspring.cloud.jdbc.datasource.TomcatJdbcDataSourceFactory;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Configuration
+public class ApplicationConfiguration {
+ @Bean
+ public RdsInstanceConfigurer instanceConfigurer() {
+ return ()-> {
+ TomcatJdbcDataSourceFactory dataSourceFactory = new TomcatJdbcDataSourceFactory();
+ dataSourceFactory.setInitialSize(10);
+ dataSourceFactory.setValidationQuery("SELECT 1 FROM DUAL");
+ return dataSourceFactory;
+ };
+ }
+}
diff --git a/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/SpringcloudrdsApplication.java b/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/SpringcloudrdsApplication.java
new file mode 100644
index 000000000..1bf148b26
--- /dev/null
+++ b/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/SpringcloudrdsApplication.java
@@ -0,0 +1,13 @@
+package io.pratik.springcloudrds;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringcloudrdsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringcloudrdsApplication.class, args);
+ }
+
+}
diff --git a/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/SystemRepository.java b/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/SystemRepository.java
new file mode 100644
index 000000000..38575f38e
--- /dev/null
+++ b/aws/springcloudrds/src/main/java/io/pratik/springcloudrds/SystemRepository.java
@@ -0,0 +1,58 @@
+/**
+ *
+ */
+package io.pratik.springcloudrds;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class SystemRepository {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ public SystemRepository(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ public String getCurrentDate() {
+ String result = jdbcTemplate.queryForObject("SELECT CURRENT_DATE FROM DUAL", new RowMapper(){
+
+ @Override
+ public String mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return rs.getString(1);
+ }
+
+ });
+ return result;
+ }
+
+
+ @Transactional(readOnly = true)
+ public List getUsers(){
+ List result = jdbcTemplate.query("SELECT USER() FROM DUAL", new RowMapper(){
+
+ @Override
+ public String mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return rs.getString(1);
+ }
+
+ });
+ return result;
+ }
+
+}
diff --git a/aws/springcloudrds/src/main/resources/application.properties b/aws/springcloudrds/src/main/resources/application.properties
new file mode 100644
index 000000000..82829d9b5
--- /dev/null
+++ b/aws/springcloudrds/src/main/resources/application.properties
@@ -0,0 +1,10 @@
+cloud.aws.credentials.profile-name=pratikpoc
+cloud.aws.region.auto=false
+cloud.aws.region.static=us-east-1
+
+cloud.aws.rds.instances[0].db-instance-identifier=testinstance
+cloud.aws.rds.instances[0].username=pocadmin
+cloud.aws.rds.instances[0].password=pocadmin
+cloud.aws.rds.instances[0].databaseName=mysql
+
+cloud.aws.rds.instances[0].readReplicaSupport=true
diff --git a/aws/springcloudrds/src/main/resources/beanconfig.xml b/aws/springcloudrds/src/main/resources/beanconfig.xml
new file mode 100644
index 000000000..731bbeb5d
--- /dev/null
+++ b/aws/springcloudrds/src/main/resources/beanconfig.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws/springcloudrds/src/test/java/io/pratik/springcloudrds/SpringcloudrdsApplicationTests.java b/aws/springcloudrds/src/test/java/io/pratik/springcloudrds/SpringcloudrdsApplicationTests.java
new file mode 100644
index 000000000..ef533d4a9
--- /dev/null
+++ b/aws/springcloudrds/src/test/java/io/pratik/springcloudrds/SpringcloudrdsApplicationTests.java
@@ -0,0 +1,19 @@
+package io.pratik.springcloudrds;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringcloudrdsApplicationTests {
+
+ @Autowired
+ private SystemRepository systemRepository;
+
+ @Test
+ void testCurrentDate() {
+ String currentDate = systemRepository.getCurrentDate();
+ System.out.println("currentDate "+currentDate);
+ }
+
+}
diff --git a/aws/springcloudses/.mvn/wrapper/MavenWrapperDownloader.java b/aws/springcloudses/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/springcloudses/.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/aws/springcloudses/.mvn/wrapper/maven-wrapper.jar b/aws/springcloudses/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/springcloudses/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/springcloudses/.mvn/wrapper/maven-wrapper.properties b/aws/springcloudses/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/springcloudses/.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/aws/springcloudses/README.md b/aws/springcloudses/README.md
new file mode 100644
index 000000000..828d9a823
--- /dev/null
+++ b/aws/springcloudses/README.md
@@ -0,0 +1,10 @@
+# Working with AWS SES and Spring Cloud
+
+Example code for using Spring Cloud AWS SES
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Working with AWS SES and Spring Cloud](https://reflectoring.io/spring-cloud-aws-ses/)
+
diff --git a/aws/springcloudses/mvnw b/aws/springcloudses/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/springcloudses/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/aws/springcloudses/mvnw.cmd b/aws/springcloudses/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/springcloudses/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/aws/springcloudses/pom.xml b/aws/springcloudses/pom.xml
new file mode 100644
index 000000000..27429724f
--- /dev/null
+++ b/aws/springcloudses/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.1
+
+
+ io.pratik
+ springses
+ 0.0.1-SNAPSHOT
+ springses
+ Demo project for Sending email with SES
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ io.awspring.cloud
+ spring-cloud-starter-aws-ses
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ 2.3.0
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
+
diff --git a/aws/springcloudses/src/main/java/io/pratik/springses/MailConfig.java b/aws/springcloudses/src/main/java/io/pratik/springses/MailConfig.java
new file mode 100644
index 000000000..0ddf625e6
--- /dev/null
+++ b/aws/springcloudses/src/main/java/io/pratik/springses/MailConfig.java
@@ -0,0 +1,43 @@
+/**
+ *
+ */
+package io.pratik.springses;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mail.MailSender;
+import org.springframework.mail.javamail.JavaMailSender;
+
+import com.amazonaws.auth.profile.ProfileCredentialsProvider;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
+import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
+
+import io.awspring.cloud.ses.SimpleEmailServiceJavaMailSender;
+import io.awspring.cloud.ses.SimpleEmailServiceMailSender;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Configuration
+public class MailConfig {
+ @Bean
+ public AmazonSimpleEmailService amazonSimpleEmailService() {
+
+ return AmazonSimpleEmailServiceClientBuilder.standard()
+ .withCredentials(new ProfileCredentialsProvider("pratikpoc"))
+ .withRegion(Regions.US_EAST_1)
+ .build();
+ }
+
+ @Bean
+ public JavaMailSender javaMailSender(AmazonSimpleEmailService amazonSimpleEmailService) {
+ return new SimpleEmailServiceJavaMailSender(amazonSimpleEmailService);
+ }
+
+ @Bean
+ public MailSender mailSender(AmazonSimpleEmailService amazonSimpleEmailService) {
+ return new SimpleEmailServiceMailSender(amazonSimpleEmailService);
+ }
+}
diff --git a/aws/springcloudses/src/main/java/io/pratik/springses/NotificationService.java b/aws/springcloudses/src/main/java/io/pratik/springses/NotificationService.java
new file mode 100644
index 000000000..aaa9128c2
--- /dev/null
+++ b/aws/springcloudses/src/main/java/io/pratik/springses/NotificationService.java
@@ -0,0 +1,53 @@
+/**
+ *
+ */
+package io.pratik.springses;
+
+import javax.mail.internet.MimeMessage;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.mail.MailSender;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.mail.javamail.MimeMessagePreparator;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class NotificationService {
+
+ @Autowired
+ private MailSender mailSender;
+
+ @Autowired
+ private JavaMailSender javaMailSender;
+
+ public void sendMailMessage(final SimpleMailMessage simpleMailMessage) {
+ System.out.println("mailSender "+mailSender.getClass().getName());
+ this.mailSender.send(simpleMailMessage);
+ }
+
+ public void sendMailMessageWithAttachments() {
+ this.javaMailSender.send(new MimeMessagePreparator() {
+
+ @Override
+ public void prepare(MimeMessage mimeMessage) throws Exception {
+ MimeMessageHelper helper =
+ new MimeMessageHelper(mimeMessage, true, "UTF-8");
+ helper.addTo("foo@bar.com");
+ helper.setFrom("bar@baz.com");
+
+ InputStreamSource data = new ByteArrayResource("".getBytes());
+ helper.addAttachment("test.txt", data );
+ helper.setSubject("test subject with attachment");
+ helper.setText("mime body", false);
+ }
+ });
+ }
+}
diff --git a/aws/springcloudses/src/main/java/io/pratik/springses/SpringsesApplication.java b/aws/springcloudses/src/main/java/io/pratik/springses/SpringsesApplication.java
new file mode 100644
index 000000000..03ced6c06
--- /dev/null
+++ b/aws/springcloudses/src/main/java/io/pratik/springses/SpringsesApplication.java
@@ -0,0 +1,13 @@
+package io.pratik.springses;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringsesApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringsesApplication.class, args);
+ }
+
+}
diff --git a/aws/springcloudses/src/main/resources/application.properties b/aws/springcloudses/src/main/resources/application.properties
new file mode 100644
index 000000000..7f88691f7
--- /dev/null
+++ b/aws/springcloudses/src/main/resources/application.properties
@@ -0,0 +1,4 @@
+cloud.aws.credentials.profile-name=pratikpoc
+cloud.aws.region.auto=false
+cloud.aws.region.static=us-east-1
+
diff --git a/aws/springcloudses/src/test/java/io/pratik/springses/NotificationServiceTest.java b/aws/springcloudses/src/test/java/io/pratik/springses/NotificationServiceTest.java
new file mode 100644
index 000000000..29e835613
--- /dev/null
+++ b/aws/springcloudses/src/test/java/io/pratik/springses/NotificationServiceTest.java
@@ -0,0 +1,61 @@
+/**
+ *
+ */
+package io.pratik.springses;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mail.SimpleMailMessage;
+
+/**
+ * @author pratikdas
+ *
+ */
+@SpringBootTest
+class NotificationServiceTest {
+
+ @Autowired
+ private NotificationService notificationService;
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @BeforeEach
+ void setUp() throws Exception {
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testSendMail() {
+ SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
+ simpleMailMessage.setFrom("pratikd2000@gmail.com");
+ simpleMailMessage.setTo("pratikd2027@gmail.com");
+ simpleMailMessage.setSubject("test subject");
+ simpleMailMessage.setText("test text");
+
+ notificationService.sendMailMessage(simpleMailMessage);
+ }
+
+ @Test
+ void testSendMailWithAttachments() {
+ SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
+ simpleMailMessage.setFrom("pratikd2000@gmail.com");
+ simpleMailMessage.setTo("pratikd2027@gmail.com");
+ simpleMailMessage.setSubject("test subject");
+ simpleMailMessage.setText("test text");
+
+ notificationService.sendMailMessage(simpleMailMessage);
+ }
+
+}
diff --git a/aws/springcloudses/src/test/java/io/pratik/springses/SpringsesApplicationTests.java b/aws/springcloudses/src/test/java/io/pratik/springses/SpringsesApplicationTests.java
new file mode 100644
index 000000000..f27d9efd5
--- /dev/null
+++ b/aws/springcloudses/src/test/java/io/pratik/springses/SpringsesApplicationTests.java
@@ -0,0 +1,13 @@
+package io.pratik.springses;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringsesApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/aws/springcloudsqs/.mvn/wrapper/MavenWrapperDownloader.java b/aws/springcloudsqs/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/springcloudsqs/.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/aws/springcloudsqs/.mvn/wrapper/maven-wrapper.jar b/aws/springcloudsqs/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/springcloudsqs/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/springcloudsqs/.mvn/wrapper/maven-wrapper.properties b/aws/springcloudsqs/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/springcloudsqs/.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/aws/springcloudsqs/README.md b/aws/springcloudsqs/README.md
new file mode 100644
index 000000000..69f0e05e6
--- /dev/null
+++ b/aws/springcloudsqs/README.md
@@ -0,0 +1,10 @@
+# Working with AWS SQS and Spring Cloud
+
+Example code for using Spring Cloud AWS Messaging
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Working with AWS SQS and Spring Cloud](https://reflectoring.io/spring-cloud-aws-sqs/)
+
diff --git a/aws/springcloudsqs/mvnw b/aws/springcloudsqs/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/springcloudsqs/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/aws/springcloudsqs/mvnw.cmd b/aws/springcloudsqs/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/springcloudsqs/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/aws/springcloudsqs/pom.xml b/aws/springcloudsqs/pom.xml
new file mode 100644
index 000000000..51903b47c
--- /dev/null
+++ b/aws/springcloudsqs/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.5
+
+
+ io.pratik
+ springcloudsqs
+ 0.0.1-SNAPSHOT
+ springcloudsqs
+ Demo project for Spring Cloud AWS SQS
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ io.awspring.cloud
+ spring-cloud-starter-aws-messaging
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ 2.3.0
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
+
diff --git a/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/CustomSqsConfiguration.java b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/CustomSqsConfiguration.java
new file mode 100644
index 000000000..781f8fb98
--- /dev/null
+++ b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/CustomSqsConfiguration.java
@@ -0,0 +1,53 @@
+/**
+ *
+ */
+package io.pratik.springcloudsqs;
+
+import java.util.Collections;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.converter.MappingJackson2MessageConverter;
+import org.springframework.messaging.converter.MessageConverter;
+import org.springframework.messaging.handler.annotation.support.PayloadMethodArgumentResolver;
+
+import com.amazonaws.services.sqs.AmazonSQSAsync;
+import com.amazonaws.services.sqs.buffered.AmazonSQSBufferedAsyncClient;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.awspring.cloud.messaging.config.QueueMessageHandlerFactory;
+import io.awspring.cloud.messaging.core.QueueMessagingTemplate;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Configuration
+public class CustomSqsConfiguration {
+
+ @Bean
+ public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSQSAsync) {
+ return new QueueMessagingTemplate(amazonSQSAsync);
+ }
+
+ @Bean
+ public QueueMessageHandlerFactory queueMessageHandlerFactory(final ObjectMapper mapper,
+ final AmazonSQSAsync amazonSQSAsync) {
+ final QueueMessageHandlerFactory queueHandlerFactory = new QueueMessageHandlerFactory();
+ queueHandlerFactory.setAmazonSqs(amazonSQSAsync);
+
+ queueHandlerFactory.setArgumentResolvers(
+ Collections.singletonList(new PayloadMethodArgumentResolver(jackson2MessageConverter(mapper))));
+ return queueHandlerFactory;
+ }
+
+ private MessageConverter jackson2MessageConverter(final ObjectMapper mapper) {
+
+ final MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
+
+ // set strict content type match to false to enable the listener to handle AWS events
+ converter.setStrictContentTypeMatch(false);
+ converter.setObjectMapper(mapper);
+ return converter;
+ }
+}
diff --git a/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageReceiver.java b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageReceiver.java
new file mode 100644
index 000000000..670bfb622
--- /dev/null
+++ b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageReceiver.java
@@ -0,0 +1,42 @@
+/**
+ *
+ */
+package io.pratik.springcloudsqs;
+
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Service;
+
+import com.amazonaws.services.s3.event.S3EventNotification;
+
+import io.awspring.cloud.messaging.listener.SqsMessageDeletionPolicy;
+import io.awspring.cloud.messaging.listener.annotation.SqsListener;
+import io.pratik.springcloudsqs.models.SignupEvent;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Slf4j
+@Service
+public class MessageReceiver {
+
+ @SqsListener(value = "testQueue", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS )
+ public void receiveStringMessage(final String message,
+ @Header("SenderId") String senderId) {
+ log.info("message received {} {}",senderId,message);
+ }
+
+ @SqsListener(value = "testObjectQueue", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS )
+ public void receiveObjectMessage(final SignupEvent message,
+ @Header("SenderId") String senderId) {
+ log.info("object message received {} {}",senderId,message);
+ }
+
+ @SqsListener(value = "testS3Queue", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
+ public void receiveS3Event(S3EventNotification s3EventNotificationRecord) {
+ S3EventNotification.S3Entity s3Entity = s3EventNotificationRecord.getRecords().get(0).getS3();
+ String objectKey = s3Entity.getObject().getKey();
+ log.info("s3 event::objectKey:: {}",objectKey);
+ }
+}
diff --git a/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageSender.java b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageSender.java
new file mode 100644
index 000000000..39cffd050
--- /dev/null
+++ b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageSender.java
@@ -0,0 +1,52 @@
+/**
+ *
+ */
+package io.pratik.springcloudsqs;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+import com.amazonaws.services.sqs.AmazonSQSAsync;
+
+import io.awspring.cloud.messaging.core.QueueMessageChannel;
+import io.awspring.cloud.messaging.core.QueueMessagingTemplate;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class MessageSender {
+ private static final Logger logger = LoggerFactory.getLogger(MessageSender.class);
+
+ private static final String QUEUE_NAME = "https://sqs.us-east-1.amazonaws.com/474715013863/testQueue";
+
+ @Autowired
+ private final AmazonSQSAsync amazonSqs;
+
+ @Autowired
+ public MessageSender(final AmazonSQSAsync amazonSQSAsync) {
+ this.amazonSqs = amazonSQSAsync;
+ }
+
+ public boolean send(final String messagePayload) {
+ MessageChannel messageChannel = new QueueMessageChannel(amazonSqs, QUEUE_NAME);
+
+ Message msg = MessageBuilder.withPayload(messagePayload)
+ .setHeader("sender", "app1")
+ .setHeaderIfAbsent("country", "AE")
+ .build();
+
+ long waitTimeoutMillis = 5000;
+ boolean sentStatus = messageChannel.send(msg,waitTimeoutMillis);
+ logger.info("message sent");
+ return sentStatus;
+ }
+
+}
diff --git a/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageSenderWithTemplate.java b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageSenderWithTemplate.java
new file mode 100644
index 000000000..459e5acc2
--- /dev/null
+++ b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/MessageSenderWithTemplate.java
@@ -0,0 +1,45 @@
+/**
+ *
+ */
+package io.pratik.springcloudsqs;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Service;
+
+import io.awspring.cloud.messaging.core.QueueMessagingTemplate;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Slf4j
+@Service
+public class MessageSenderWithTemplate {
+ private static final String TEST_QUEUE = "testQueue";
+
+ @Autowired
+ private QueueMessagingTemplate messagingTemplate;
+
+ public void send(final String messagePayload) {
+
+ Message msg = MessageBuilder.withPayload(messagePayload)
+ .setHeader("sender", "app1")
+ .setHeaderIfAbsent("country", "AE")
+ .build();
+ messagingTemplate.convertAndSend(TEST_QUEUE, msg);
+ log.info("message sent");
+ }
+
+ public void sendToFifoQueue(final String messagePayload, final String messageGroupID, final String messageDedupID) {
+
+ Message msg = MessageBuilder.withPayload(messagePayload)
+ .setHeader("message-group-id", messageGroupID)
+ .setHeader("message-deduplication-id", messageDedupID)
+ .build();
+ messagingTemplate.convertAndSend(TEST_QUEUE, msg);
+ log.info("message sent");
+ }
+}
diff --git a/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/SpringcloudsqsApplication.java b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/SpringcloudsqsApplication.java
new file mode 100644
index 000000000..15e262fc6
--- /dev/null
+++ b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/SpringcloudsqsApplication.java
@@ -0,0 +1,12 @@
+package io.pratik.springcloudsqs;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringcloudsqsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringcloudsqsApplication.class, args);
+ }
+}
diff --git a/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/models/SignupEvent.java b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/models/SignupEvent.java
new file mode 100644
index 000000000..be6ba897a
--- /dev/null
+++ b/aws/springcloudsqs/src/main/java/io/pratik/springcloudsqs/models/SignupEvent.java
@@ -0,0 +1,19 @@
+/**
+ *
+ */
+package io.pratik.springcloudsqs.models;
+
+import lombok.Data;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Data
+public class SignupEvent {
+
+ private String signupTime;
+ private String userName;
+ private String email;
+
+}
diff --git a/aws/springcloudsqs/src/main/resources/application.properties b/aws/springcloudsqs/src/main/resources/application.properties
new file mode 100644
index 000000000..a15033aa3
--- /dev/null
+++ b/aws/springcloudsqs/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+cloud.aws.credentials.profile-name=default
+cloud.aws.region.auto=false
+cloud.aws.region.static=us-east-1
diff --git a/aws/springcloudsqs/src/test/java/io/pratik/springcloudsqs/SpringcloudsqsApplicationTests.java b/aws/springcloudsqs/src/test/java/io/pratik/springcloudsqs/SpringcloudsqsApplicationTests.java
new file mode 100644
index 000000000..02a866fd0
--- /dev/null
+++ b/aws/springcloudsqs/src/test/java/io/pratik/springcloudsqs/SpringcloudsqsApplicationTests.java
@@ -0,0 +1,34 @@
+package io.pratik.springcloudsqs;
+
+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;
+
+@SpringBootTest
+class SpringcloudsqsApplicationTests {
+ private static final Logger logger = LoggerFactory.getLogger(SpringcloudsqsApplicationTests.class.getName());
+
+ @Autowired
+ private MessageSender messageSender;
+
+ @Autowired
+ private MessageSenderWithTemplate messageSenderWithTemplate;
+
+ @Test
+ void contextLoads() {
+ }
+
+ void send_message_with_message_template() {
+ logger.info("test with message template.");
+ messageSenderWithTemplate.send("Test Message1");
+ }
+
+ @Test
+ void send_message_with_message_channel() {
+ logger.info("test with message channel.");
+ messageSender.send("Test msg");
+ }
+
+}
diff --git a/aws/springcloudwatch/.mvn/wrapper/MavenWrapperDownloader.java b/aws/springcloudwatch/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/springcloudwatch/.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/aws/springcloudwatch/.mvn/wrapper/maven-wrapper.jar b/aws/springcloudwatch/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/springcloudwatch/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/springcloudwatch/.mvn/wrapper/maven-wrapper.properties b/aws/springcloudwatch/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/springcloudwatch/.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/aws/springcloudwatch/README.md b/aws/springcloudwatch/README.md
new file mode 100644
index 000000000..c2e7e20d3
--- /dev/null
+++ b/aws/springcloudwatch/README.md
@@ -0,0 +1,9 @@
+# Publishing Metrics from Spring Boot Application to Amazon CloudWatch
+
+Example code for using Spring Boot Application with Amazon CloudWatch
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Publishing Metrics from Spring Boot Application to Amazon CloudWatch](https://reflectoring.io/spring-aws-cloudwatch/)
diff --git a/aws/springcloudwatch/mvnw b/aws/springcloudwatch/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/springcloudwatch/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/aws/springcloudwatch/mvnw.cmd b/aws/springcloudwatch/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/springcloudwatch/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/aws/springcloudwatch/pom.xml b/aws/springcloudwatch/pom.xml
new file mode 100644
index 000000000..fd13456d9
--- /dev/null
+++ b/aws/springcloudwatch/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.3
+
+
+ io.pratik
+ metricscapture
+ 0.0.1-SNAPSHOT
+ metricscapture
+ Demo project for capturing cloudwatch metrics in Spring Boot
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ io.micrometer
+ micrometer-core
+
+
+
+
+ io.micrometer
+ micrometer-registry-cloudwatch2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ 2.3.0
+ pom
+ import
+
+
+ software.amazon.awssdk
+ bom
+ 2.17.14
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/AppConfig.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/AppConfig.java
new file mode 100644
index 000000000..e67fb874f
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/AppConfig.java
@@ -0,0 +1,62 @@
+/**
+ *
+ */
+package io.pratik.metricscapture;
+
+import java.time.Duration;
+import java.util.Map;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import io.micrometer.cloudwatch2.CloudWatchConfig;
+import io.micrometer.cloudwatch2.CloudWatchMeterRegistry;
+import io.micrometer.core.instrument.Clock;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Configuration
+public class AppConfig {
+
+
+
+ @Bean
+ public CloudWatchAsyncClient cloudWatchAsyncClient() {
+ return CloudWatchAsyncClient.builder().region(Region.US_EAST_1)
+ .credentialsProvider(ProfileCredentialsProvider.create("pratikpoc")).build();
+ }
+
+ @Bean
+ public MeterRegistry getMeterRegistry() {
+ CloudWatchConfig cloudWatchConfig = setupCloudWatchConfig();
+
+ MeterRegistry meterRegistry = new CloudWatchMeterRegistry(cloudWatchConfig, Clock.SYSTEM,
+ cloudWatchAsyncClient());
+
+ return meterRegistry;
+ }
+
+ private CloudWatchConfig setupCloudWatchConfig() {
+ CloudWatchConfig cloudWatchConfig = new CloudWatchConfig() {
+
+ private Map configuration
+ = Map.of("cloudwatch.namespace", "productsApp",
+ "cloudwatch.step", Duration.ofMinutes(1).toString());
+
+ @Override
+ public String get(String key) {
+ return configuration.get(key);
+ }
+ };
+ return cloudWatchConfig;
+ }
+
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/MetricPublisher.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/MetricPublisher.java
new file mode 100644
index 000000000..2c1913a28
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/MetricPublisher.java
@@ -0,0 +1,87 @@
+/**
+ *
+ */
+package io.pratik.metricscapture;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import io.pratik.metricscapture.models.MetricTag;
+import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
+import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
+import software.amazon.awssdk.services.cloudwatch.model.Dimension;
+import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
+import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataRequest;
+import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class MetricPublisher {
+
+ private CloudWatchAsyncClient cloudWatchAsyncClient;
+
+
+ @Autowired
+ public MetricPublisher(CloudWatchAsyncClient cloudWatchAsyncClient) {
+ super();
+ this.cloudWatchAsyncClient = cloudWatchAsyncClient;
+ }
+
+
+
+ public void putMetricData(final String nameSpace,
+ final String metricName,
+ final Double dataPoint,
+ final List metricTags) {
+
+ try {
+
+ List dimensions = metricTags
+ .stream()
+ .map((metricTag)->{
+ return Dimension
+ .builder()
+ .name(metricTag.getName())
+ .value(metricTag.getValue())
+ .build();
+ }).collect(Collectors.toList());
+
+ // Set an Instant object
+ String time = ZonedDateTime
+ .now(ZoneOffset.UTC)
+ .format(DateTimeFormatter.ISO_INSTANT);
+ Instant instant = Instant.parse(time);
+
+ MetricDatum datum = MetricDatum
+ .builder()
+ .metricName(metricName)
+ .unit(StandardUnit.NONE)
+ .value(dataPoint)
+ .timestamp(instant)
+ .dimensions(dimensions)
+ .build();
+
+ PutMetricDataRequest request =
+ PutMetricDataRequest
+ .builder()
+ .namespace(nameSpace)
+ .metricData(datum)
+ .build();
+
+ cloudWatchAsyncClient.putMetricData(request);
+
+ } catch (CloudWatchException e) {
+ System.err.println(e.awsErrorDetails().errorMessage());
+ }
+ }
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/MetricscaptureApplication.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/MetricscaptureApplication.java
new file mode 100644
index 000000000..230e325ff
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/MetricscaptureApplication.java
@@ -0,0 +1,27 @@
+package io.pratik.metricscapture;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootApplication
+@EnableScheduling
+public class MetricscaptureApplication {
+
+ @Bean
+ RestTemplate restTemplate(RestTemplateBuilder builder) {
+ return builder
+ .messageConverters(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter())
+ .build();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(MetricscaptureApplication.class, args);
+ }
+
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/PricingEngine.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/PricingEngine.java
new file mode 100644
index 000000000..4d2d9ab37
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/PricingEngine.java
@@ -0,0 +1,34 @@
+/**
+ *
+ */
+package io.pratik.metricscapture;
+
+import java.util.Random;
+
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class PricingEngine {
+
+ private Double price;
+
+ public Double getProductPrice() {
+ return price;
+
+ }
+
+ @Scheduled(fixedRate = 70000)
+ public void computePrice() {
+
+ Random random = new Random();
+ price = random.nextDouble() * 100;
+ System.out.println("computing price "+price);
+
+ }
+
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/ProductController.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/ProductController.java
new file mode 100644
index 000000000..875db2e18
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/ProductController.java
@@ -0,0 +1,89 @@
+/**
+ *
+ */
+package io.pratik.metricscapture;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import io.pratik.metricscapture.models.Order;
+import io.pratik.metricscapture.models.Product;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author pratikdas
+ *
+ */
+@RestController
+@Slf4j
+public class ProductController {
+ private Counter pageViewsCounter;
+ private Timer productTimer;
+ private Gauge priceGauge;
+
+ private MeterRegistry meterRegistry;
+
+ private PricingEngine pricingEngine;
+
+ @Autowired
+ ProductController(MeterRegistry meterRegistry, PricingEngine pricingEngine){
+
+ this.meterRegistry = meterRegistry;
+ this.pricingEngine = pricingEngine;
+
+ priceGauge = Gauge
+ .builder("product.price", pricingEngine ,
+ (pe)->{return pe != null? pe.getProductPrice() : null;})
+ .description("Product price")
+ .baseUnit("ms")
+ .register(meterRegistry);
+
+ pageViewsCounter = meterRegistry
+ .counter("PAGE_VIEWS.ProductList");
+
+ productTimer = meterRegistry
+ .timer("execution.time.fetchProducts");
+
+ }
+
+
+
+
+ @GetMapping("/products")
+ @ResponseBody
+ public List fetchProducts() {
+ long startTime = System.currentTimeMillis();
+
+ List products = fetchProductsFromStore();
+
+ // increment page views counter
+ pageViewsCounter.increment();
+
+ // record time to execute the method
+ System.out.println("productTimer "+productTimer + " " + pageViewsCounter + " " + priceGauge);
+ productTimer.record(Duration.ofMillis(System.currentTimeMillis() - startTime));
+
+ return products;
+ }
+
+ private List fetchProductsFromStore(){
+ List products = new ArrayList();
+ products.add(Product.builder().name("Television").build());
+ products.add(Product.builder().name("Book").build());
+ return products;
+ }
+
+
+
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/MetricTag.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/MetricTag.java
new file mode 100644
index 000000000..2a8317bda
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/MetricTag.java
@@ -0,0 +1,25 @@
+/**
+ *
+ */
+package io.pratik.metricscapture.models;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class MetricTag {
+ private String name;
+ private String value;
+ public MetricTag(String name, String value) {
+ super();
+ this.name = name;
+ this.value = value;
+ }
+ public String getName() {
+ return name;
+ }
+ public String getValue() {
+ return value;
+ }
+
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/Order.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/Order.java
new file mode 100644
index 000000000..e7317d9c5
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/Order.java
@@ -0,0 +1,21 @@
+/**
+ *
+ */
+package io.pratik.metricscapture.models;
+
+import java.util.List;
+
+import lombok.Builder;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Builder
+public class Order {
+ private List products;
+
+ private String orderDate;
+
+
+}
diff --git a/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/Product.java b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/Product.java
new file mode 100644
index 000000000..8dbbd00e5
--- /dev/null
+++ b/aws/springcloudwatch/src/main/java/io/pratik/metricscapture/models/Product.java
@@ -0,0 +1,19 @@
+/**
+ *
+ */
+package io.pratik.metricscapture.models;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Builder
+@Data
+public class Product {
+ private String name;
+ private Double price;
+
+}
diff --git a/aws/springcloudwatch/src/main/resources/application.properties b/aws/springcloudwatch/src/main/resources/application.properties
new file mode 100644
index 000000000..2640097c8
--- /dev/null
+++ b/aws/springcloudwatch/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+cloud.aws.credentials.profile-name=pratikpoc
+cloud.aws.region.auto=false
+cloud.aws.region.static=us-east-1
+
+logging.level.io.micrometer=DEBUG
+
+
+
diff --git a/aws/springcloudwatch/src/test/java/io/pratik/metricscapture/MetricscaptureApplicationTests.java b/aws/springcloudwatch/src/test/java/io/pratik/metricscapture/MetricscaptureApplicationTests.java
new file mode 100644
index 000000000..7610f1b11
--- /dev/null
+++ b/aws/springcloudwatch/src/test/java/io/pratik/metricscapture/MetricscaptureApplicationTests.java
@@ -0,0 +1,13 @@
+package io.pratik.metricscapture;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class MetricscaptureApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/aws/springdynamodb/README.md b/aws/springdynamodb/README.md
new file mode 100644
index 000000000..dced30abe
--- /dev/null
+++ b/aws/springdynamodb/README.md
@@ -0,0 +1,20 @@
+# Working with AWS DynamoDB and Spring
+
+Example code for using Spring AWS DynamoDB
+
+## Blog posts
+
+Blog posts about this topic:
+
+* [Working with AWS DynamoDB and Spring ](https://reflectoring.io/spring-dynamodb/)
+
+This is a nested Maven project composed of two modules:
+
+1. dynamodbec: Contains a Spring Boot project which uses Enhanced DynamoDB Client to make database operations on DynamoDB.
+
+2. dynamodbspringdata: Contains a Spring Boot project which uses Spring Data to perform operations on DynamoDB.
+
+In both projects, the Spring Boot dependency is added in the `dependencyManagement` block in `pom.xml`
+.
+
+
diff --git a/aws/springdynamodb/dynamodbec/.mvn/wrapper/MavenWrapperDownloader.java b/aws/springdynamodb/dynamodbec/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/.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/aws/springdynamodb/dynamodbec/.mvn/wrapper/maven-wrapper.jar b/aws/springdynamodb/dynamodbec/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/springdynamodb/dynamodbec/.mvn/wrapper/maven-wrapper.properties b/aws/springdynamodb/dynamodbec/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/.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/aws/springdynamodb/dynamodbec/mvnw b/aws/springdynamodb/dynamodbec/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/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/aws/springdynamodb/dynamodbec/mvnw.cmd b/aws/springdynamodb/dynamodbec/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/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/aws/springdynamodb/dynamodbec/pom.xml b/aws/springdynamodb/dynamodbec/pom.xml
new file mode 100644
index 000000000..c99704f09
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+ io.pratik
+ dynamodbapp
+ 0.0.1-SNAPSHOT
+
+ io.pratik
+ dynamodbec
+ 0.0.1-SNAPSHOT
+ dynamodbec
+ Demo project for Spring Boot for dynamodb enhanced client
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ software.amazon.awssdk
+ dynamodb-enhanced
+ 2.16.74
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.4.0
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ maven-compiler-plugin
+ 3.5.1
+
+ 11
+ 11
+
+
+
+
+
+
diff --git a/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/DynamodbappsApplication.java b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/DynamodbappsApplication.java
new file mode 100644
index 000000000..d9a6a2458
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/DynamodbappsApplication.java
@@ -0,0 +1,12 @@
+package io.pratik.dynamodbapps;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DynamodbappsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DynamodbappsApplication.class, args);
+ }
+}
diff --git a/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/OrderRepository.java b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/OrderRepository.java
new file mode 100644
index 000000000..da9c37a5c
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/OrderRepository.java
@@ -0,0 +1,107 @@
+/**
+ *
+ */
+package io.pratik.dynamodbapps;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import io.pratik.dynamodbapps.models.Order;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
+import software.amazon.awssdk.enhanced.dynamodb.Expression;
+import software.amazon.awssdk.enhanced.dynamodb.Key;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
+import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Repository
+public class OrderRepository {
+
+ @Autowired
+ private DynamoDbEnhancedClient dynamoDbenhancedClient;
+
+ public OrderRepository() {
+ super();
+ }
+
+ // Store this order item in the database
+ public void save(final Order order) {
+ DynamoDbTable orderTable = getTable();
+ orderTable.putItem(order);
+ }
+
+ /**
+ * @return
+ */
+ private DynamoDbTable getTable() {
+ DynamoDbTable orderTable = dynamoDbenhancedClient.table("Order", TableSchema.fromBean(Order.class));
+ return orderTable;
+ }
+
+ // Retrieve a single order item from the database
+ public Order getOrder(final String customerID, final String orderID) {
+ DynamoDbTable orderTable = getTable();
+ // Construct the key with partition and sort key
+ Key key = Key.builder().partitionValue(customerID).sortValue(orderID).build();
+
+ Order order = orderTable.getItem(key);
+
+ return order;
+ }
+
+ public void deleteOrder(final String customerID, final String orderID) {
+ DynamoDbTable orderTable = getTable();
+
+ Key key = Key.builder().partitionValue(customerID).sortValue(orderID).build();
+
+ DeleteItemEnhancedRequest deleteRequest = DeleteItemEnhancedRequest
+ .builder()
+ .key(key)
+ .build();
+
+ orderTable.deleteItem(deleteRequest);
+ }
+
+ public PageIterable scanOrders(final String customerID, final String orderID) {
+ DynamoDbTable orderTable = getTable();
+
+ return orderTable.scan();
+ }
+
+ public PageIterable findOrdersByValue(final String customerID, final double orderValue) {
+ DynamoDbTable orderTable = getTable();
+
+ AttributeValue attributeValue = AttributeValue.builder()
+ .n(String.valueOf(orderValue))
+ .build();
+
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":value", attributeValue);
+
+ Expression expression = Expression.builder()
+ .expression("orderValue > :value")
+ .expressionValues(expressionValues)
+ .build();
+
+ // Create a QueryConditional object that is used in the query operation
+ QueryConditional queryConditional = QueryConditional
+ .keyEqualTo(Key.builder().partitionValue(customerID)
+ .build());
+
+ // Get items in the Customer table and write out the ID value
+ PageIterable results = orderTable.query(r -> r.queryConditional(queryConditional).filterExpression(expression));
+ return results;
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/config/AppConfig.java b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/config/AppConfig.java
new file mode 100644
index 000000000..db423bf55
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/config/AppConfig.java
@@ -0,0 +1,37 @@
+/**
+ *
+ */
+package io.pratik.dynamodbapps.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Configuration
+public class AppConfig {
+
+ @Bean
+ public DynamoDbClient getDynamoDbClient() {
+ AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().profileName("pratikpoc")
+ .build();
+
+ return DynamoDbClient.builder().region(Region.US_EAST_1).credentialsProvider(credentialsProvider).build();
+ }
+
+ @Bean
+ public DynamoDbEnhancedClient getDynamoDbEnhancedClient() {
+ return DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .build();
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Order.java b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Order.java
new file mode 100644
index 000000000..958cf06f8
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Order.java
@@ -0,0 +1,63 @@
+/**
+ *
+ */
+package io.pratik.dynamodbapps.models;
+
+import java.time.Instant;
+import java.util.List;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+
+/**
+ * @author pratikdas
+ *
+ */
+@DynamoDbBean
+public class Order {
+ private String customerID;
+ private String orderID;
+ private double orderValue;
+ private Instant createdDate;
+
+ private List products;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAttribute("CustomerID")
+ public String getCustomerID() {
+ return customerID;
+ }
+ public void setCustomerID(String customerID) {
+ this.customerID = customerID;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAttribute("OrderID")
+ public String getOrderID() {
+ return orderID;
+ }
+ public void setOrderID(String orderID) {
+ this.orderID = orderID;
+ }
+
+ public Instant getCreatedDate() {
+ return createdDate;
+ }
+ public void setCreatedDate(Instant createdDate) {
+ this.createdDate = createdDate;
+ }
+ public double getOrderValue() {
+ return orderValue;
+ }
+ public void setOrderValue(double orderValue) {
+ this.orderValue = orderValue;
+ }
+ public List getProducts() {
+ return products;
+ }
+ public void setProducts(List products) {
+ this.products = products;
+ }
+}
diff --git a/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Product.java b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Product.java
new file mode 100644
index 000000000..ab098da03
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Product.java
@@ -0,0 +1,38 @@
+/**
+ *
+ */
+package io.pratik.dynamodbapps.models;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+
+/**
+ * @author pratikdas
+ *
+ */
+@DynamoDbBean
+public class Product {
+ private String name;
+ private String brand;
+ private double price;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getBrand() {
+ return brand;
+ }
+ public void setBrand(String brand) {
+ this.brand = brand;
+ }
+ public double getPrice() {
+ return price;
+ }
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+
+
+}
diff --git a/aws/springdynamodb/dynamodbec/src/main/resources/application.properties b/aws/springdynamodb/dynamodbec/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/aws/springdynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/DynamodbappsApplicationTests.java b/aws/springdynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/DynamodbappsApplicationTests.java
new file mode 100644
index 000000000..9fcc5efb4
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/DynamodbappsApplicationTests.java
@@ -0,0 +1,13 @@
+package io.pratik.dynamodbapps;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class DynamodbappsApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/OrderRepositoryTest.java b/aws/springdynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/OrderRepositoryTest.java
new file mode 100644
index 000000000..68244aec4
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/OrderRepositoryTest.java
@@ -0,0 +1,83 @@
+/**
+ *
+ */
+package io.pratik.dynamodbapps;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import io.pratik.dynamodbapps.models.Order;
+import io.pratik.dynamodbapps.models.Product;
+import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
+
+/**
+ * @author pratikdas
+ *
+ */
+@SpringBootTest
+class OrderRepositoryTest {
+
+ @Autowired
+ private OrderRepository orderRepository;
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @BeforeEach
+ void setUp() throws Exception {
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testCreateOrder() {
+ Order order = new Order();
+ order.setOrderID("ORD-010");
+ order.setCustomerID("CUST-001");
+
+ List products = new ArrayList();
+
+ Product product = new Product();
+ product.setName("Television");
+ product.setBrand("samsung");
+ product.setPrice(112.56);
+ products.add(product);
+
+ product = new Product();
+ product.setName("Washing Machine");
+ product.setBrand("panasonic");
+ product.setPrice(119.99);
+ products.add(product);
+
+ order.setProducts(products );
+ order.setOrderValue(56.7);
+ order.setCreatedDate(Instant.now());
+ orderRepository.save(order);
+ }
+
+ @Test
+ void testGetOrder() {
+ Order order =
+ orderRepository.getOrder("CUST-001", "ORD-010");
+ System.out.println("order "+order.getProducts());
+ }
+
+ @Test
+ void testFindOrdersByValue() {
+ PageIterable orders =
+ orderRepository.findOrdersByValue("CUST-001", 5.0d);
+ System.out.println("orders "+orders.items());
+ }
+}
diff --git a/aws/springdynamodb/dynamodbec/target/classes/application.properties b/aws/springdynamodb/dynamodbec/target/classes/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/target/classes/application.properties
@@ -0,0 +1 @@
+
diff --git a/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/DynamodbappsApplication.class b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/DynamodbappsApplication.class
new file mode 100644
index 000000000..610609343
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/DynamodbappsApplication.class differ
diff --git a/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/OrderRepository.class b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/OrderRepository.class
new file mode 100644
index 000000000..e4a25ea11
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/OrderRepository.class differ
diff --git a/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/config/AppConfig.class b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/config/AppConfig.class
new file mode 100644
index 000000000..1e4fd488c
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/config/AppConfig.class differ
diff --git a/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/models/Order.class b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/models/Order.class
new file mode 100644
index 000000000..4db2bd2f3
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/models/Order.class differ
diff --git a/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/models/Product.class b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/models/Product.class
new file mode 100644
index 000000000..cdd8bc0fa
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/classes/io/pratik/dynamodbapps/models/Product.class differ
diff --git a/aws/springdynamodb/dynamodbec/target/dynamodbec-0.0.1-SNAPSHOT.jar b/aws/springdynamodb/dynamodbec/target/dynamodbec-0.0.1-SNAPSHOT.jar
new file mode 100644
index 000000000..463e94cc2
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/dynamodbec-0.0.1-SNAPSHOT.jar differ
diff --git a/aws/springdynamodb/dynamodbec/target/maven-archiver/pom.properties b/aws/springdynamodb/dynamodbec/target/maven-archiver/pom.properties
new file mode 100644
index 000000000..728926b94
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/target/maven-archiver/pom.properties
@@ -0,0 +1,5 @@
+#Generated by Maven
+#Thu Jun 03 12:13:17 GST 2021
+groupId=io.pratik
+artifactId=dynamodbec
+version=0.0.1-SNAPSHOT
diff --git a/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 000000000..71dfc288b
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,5 @@
+io/pratik/dynamodbapps/models/Product.class
+io/pratik/dynamodbapps/config/AppConfig.class
+io/pratik/dynamodbapps/models/Order.class
+io/pratik/dynamodbapps/OrderRepository.class
+io/pratik/dynamodbapps/DynamodbappsApplication.class
diff --git a/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 000000000..133ef2e15
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,5 @@
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/DynamodbappsApplication.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/OrderRepository.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/config/AppConfig.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Product.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/main/java/io/pratik/dynamodbapps/models/Order.java
diff --git a/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
new file mode 100644
index 000000000..924e17abc
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
@@ -0,0 +1,2 @@
+io/pratik/dynamodbapps/OrderRepositoryTest.class
+io/pratik/dynamodbapps/DynamodbappsApplicationTests.class
diff --git a/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
new file mode 100644
index 000000000..a25d9357e
--- /dev/null
+++ b/aws/springdynamodb/dynamodbec/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
@@ -0,0 +1,2 @@
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/OrderRepositoryTest.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbec/src/test/java/io/pratik/dynamodbapps/DynamodbappsApplicationTests.java
diff --git a/aws/springdynamodb/dynamodbec/target/test-classes/io/pratik/dynamodbapps/DynamodbappsApplicationTests.class b/aws/springdynamodb/dynamodbec/target/test-classes/io/pratik/dynamodbapps/DynamodbappsApplicationTests.class
new file mode 100644
index 000000000..7c57789b8
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/test-classes/io/pratik/dynamodbapps/DynamodbappsApplicationTests.class differ
diff --git a/aws/springdynamodb/dynamodbec/target/test-classes/io/pratik/dynamodbapps/OrderRepositoryTest.class b/aws/springdynamodb/dynamodbec/target/test-classes/io/pratik/dynamodbapps/OrderRepositoryTest.class
new file mode 100644
index 000000000..5755012d7
Binary files /dev/null and b/aws/springdynamodb/dynamodbec/target/test-classes/io/pratik/dynamodbapps/OrderRepositoryTest.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/MavenWrapperDownloader.java b/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/.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/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/maven-wrapper.jar b/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/maven-wrapper.properties b/aws/springdynamodb/dynamodbspringdata/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/.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/aws/springdynamodb/dynamodbspringdata/mvnw b/aws/springdynamodb/dynamodbspringdata/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/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/aws/springdynamodb/dynamodbspringdata/mvnw.cmd b/aws/springdynamodb/dynamodbspringdata/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/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/aws/springdynamodb/dynamodbspringdata/pom.xml b/aws/springdynamodb/dynamodbspringdata/pom.xml
new file mode 100644
index 000000000..5ab380fd1
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/pom.xml
@@ -0,0 +1,70 @@
+
+
+ 4.0.0
+
+ io.pratik
+ dynamodbapp
+ 0.0.1-SNAPSHOT
+
+ io.pratik
+ dynamodbspringdata
+ 0.0.1-SNAPSHOT
+ dynamodbspringdata
+ Demo project for Spring Boot
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.github.derjust
+ spring-data-dynamodb
+ 5.1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.data
+ spring-data-releasetrain
+ Lovelace-SR1
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.4.0
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ maven-compiler-plugin
+ 3.5.1
+
+ 11
+ 11
+
+
+
+
+
+
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/CustomerService.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/CustomerService.java
new file mode 100644
index 000000000..d4bad32f8
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/CustomerService.java
@@ -0,0 +1,26 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import io.pratik.dynamodbspring.models.Customer;
+import io.pratik.dynamodbspring.repositories.CustomerRepository;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class CustomerService {
+
+ @Autowired
+ private CustomerRepository customerRepository;
+
+ public void createCustomer(final Customer customer) {
+ customerRepository.save(customer);
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/DynamodbspringApplication.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/DynamodbspringApplication.java
new file mode 100644
index 000000000..0cd68aff1
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/DynamodbspringApplication.java
@@ -0,0 +1,13 @@
+package io.pratik.dynamodbspring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DynamodbspringApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DynamodbspringApplication.class, args);
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/OrderService.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/OrderService.java
new file mode 100644
index 000000000..a44846399
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/OrderService.java
@@ -0,0 +1,26 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import io.pratik.dynamodbspring.models.Order;
+import io.pratik.dynamodbspring.repositories.OrderRepository;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Service
+public class OrderService {
+
+ @Autowired
+ private OrderRepository orderRepository;
+
+ public void createOrder(final Order order) {
+ orderRepository.save(order);
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/config/AppConfig.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/config/AppConfig.java
new file mode 100644
index 000000000..e9783c3a9
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/config/AppConfig.java
@@ -0,0 +1,33 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.config;
+
+import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.profile.ProfileCredentialsProvider;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
+
+/**
+ * @author pratikdas
+ *
+ */
+@Configuration
+@EnableDynamoDBRepositories
+(basePackages = "io.pratik.dynamodbspring.repositories")
+public class AppConfig {
+
+ @Bean
+ public AmazonDynamoDB amazonDynamoDB() {
+ AWSCredentialsProvider credentials = new ProfileCredentialsProvider("pratikpoc");
+ AmazonDynamoDB amazonDynamoDB
+ = AmazonDynamoDBClientBuilder.standard().withCredentials(credentials).build();
+
+ return amazonDynamoDB;
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Customer.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Customer.java
new file mode 100644
index 000000000..5a407557e
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Customer.java
@@ -0,0 +1,53 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.models;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
+
+/**
+ * @author pratikdas
+ *
+ */
+@DynamoDBTable(tableName = "Customer")
+public class Customer {
+
+ private String customerID;
+
+ private String name;
+
+ private String email;
+
+ // Partition key
+ @DynamoDBHashKey(attributeName = "CustomerID")
+ public String getCustomerID() {
+ return customerID;
+ }
+
+ public void setCustomerID(String customerID) {
+ this.customerID = customerID;
+ }
+
+ @DynamoDBAttribute(attributeName = "Name")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @DynamoDBAttribute(attributeName = "Email")
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Order.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Order.java
new file mode 100644
index 000000000..e1faed91c
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Order.java
@@ -0,0 +1,83 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.models;
+
+import java.time.Instant;
+
+import org.springframework.data.annotation.Id;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
+
+/**
+ * @author pratikdas
+ *
+ */
+@DynamoDBTable(tableName = "Order")
+public class Order {
+ private String customerID;
+ private String orderSerial;
+ private double orderValue;
+ private Instant createdDate;
+
+ @Id
+ private OrderID orderID;
+
+
+
+ public Order() {
+ super();
+ }
+// private List products;
+
+ public Order(OrderID orderID) {
+ super();
+ this.orderID = orderID;
+ }
+ /*
+ * public OrderID getOrderID() { return orderID; } public void
+ * setOrderID(OrderID orderID) { this.orderID = orderID; }
+ */
+ @DynamoDBHashKey(attributeName = "customerID")
+ public String getCustomerID() {
+ return customerID;
+ }
+ public void setCustomerID(String customerID) {
+ this.customerID = customerID;
+ }
+
+ @DynamoDBRangeKey(attributeName = "orderID")
+ public String getOrderSerial() {
+ return orderSerial;
+ }
+ public void setOrderSerial(String orderSerial) {
+ this.orderSerial = orderSerial;
+ }
+
+ @DynamoDBAttribute
+ public Instant getCreatedDate() {
+ return createdDate;
+ }
+ public void setCreatedDate(Instant createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ @DynamoDBAttribute
+ public double getOrderValue() {
+ return orderValue;
+ }
+ public void setOrderValue(double orderValue) {
+ this.orderValue = orderValue;
+ }
+
+ /*@DynamoDBAttribute
+ public List getProducts() {
+ return products;
+ }
+ public void setProducts(List products) {
+ this.products = products;
+ }*/
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/OrderID.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/OrderID.java
new file mode 100644
index 000000000..c89f520ad
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/OrderID.java
@@ -0,0 +1,50 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.models;
+
+import java.io.Serializable;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class OrderID implements Serializable{
+
+ private static final long serialVersionUID = 1L;
+ private String customerID;
+ private String orderSerial;
+
+ public OrderID() {
+ super();
+ this.customerID = null;
+ this.orderSerial = null;
+ }
+
+ public OrderID(String customerID, String orderSerial) {
+ super();
+ this.customerID = customerID;
+ this.orderSerial = orderSerial;
+ }
+
+ @DynamoDBHashKey
+ public String getCustomerID() {
+ return customerID;
+ }
+ public void setCustomerID(String customerID) {
+ this.customerID = customerID;
+ }
+
+ @DynamoDBRangeKey
+ public String getOrderSerial() {
+ return orderSerial;
+ }
+ public void setOrderSerial(String orderSerial) {
+ this.orderSerial = orderSerial;
+ }
+
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Product.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Product.java
new file mode 100644
index 000000000..e1c5badf7
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Product.java
@@ -0,0 +1,38 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.models;
+
+//import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+
+/**
+ * @author pratikdas
+ *
+ */
+//@DynamoDbBean
+public class Product {
+ private String name;
+ private String brand;
+ private double price;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getBrand() {
+ return brand;
+ }
+ public void setBrand(String brand) {
+ this.brand = brand;
+ }
+ public double getPrice() {
+ return price;
+ }
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/CustomerRepository.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/CustomerRepository.java
new file mode 100644
index 000000000..2e7195d3c
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/CustomerRepository.java
@@ -0,0 +1,20 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.repositories;
+
+import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
+import org.springframework.data.repository.CrudRepository;
+
+import io.pratik.dynamodbspring.models.Customer;
+
+/**
+ * @author pratikdas
+ *
+ */
+@EnableScan
+public interface CustomerRepository extends
+ CrudRepository {
+
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/OrderRepository.java b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/OrderRepository.java
new file mode 100644
index 000000000..0295aa9c9
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/OrderRepository.java
@@ -0,0 +1,21 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring.repositories;
+
+import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
+import org.springframework.data.repository.CrudRepository;
+
+import io.pratik.dynamodbspring.models.Order;
+import io.pratik.dynamodbspring.models.OrderID;
+
+/**
+ * @author pratikdas
+ *
+ */
+@EnableScan
+public interface OrderRepository extends
+ CrudRepository {
+
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/main/resources/application.properties b/aws/springdynamodb/dynamodbspringdata/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/aws/springdynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/CustomerServiceTest.java b/aws/springdynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/CustomerServiceTest.java
new file mode 100644
index 000000000..b0a3a6072
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/CustomerServiceTest.java
@@ -0,0 +1,48 @@
+/**
+ *
+ */
+package io.pratik.dynamodbspring;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import io.pratik.dynamodbspring.models.Customer;
+
+/**
+ * @author pratikdas
+ *
+ */
+@SpringBootTest
+class CustomerServiceTest {
+
+ @Autowired
+ private CustomerService customerService;
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @BeforeEach
+ void setUp() throws Exception {
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testCreateCustomer() {
+ Customer customer = new Customer();
+ customer.setCustomerID("CUST-001");
+ customer.setName("pratik");
+ customer.setEmail("hgjgjh");
+ customerService.createCustomer(customer);
+ }
+
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/DynamodbspringApplicationTests.java b/aws/springdynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/DynamodbspringApplicationTests.java
new file mode 100644
index 000000000..a2fc1984f
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/DynamodbspringApplicationTests.java
@@ -0,0 +1,13 @@
+package io.pratik.dynamodbspring;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class DynamodbspringApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/application.properties b/aws/springdynamodb/dynamodbspringdata/target/classes/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/target/classes/application.properties
@@ -0,0 +1 @@
+
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/CustomerService.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/CustomerService.class
new file mode 100644
index 000000000..6e74793fa
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/CustomerService.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/DynamodbspringApplication.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/DynamodbspringApplication.class
new file mode 100644
index 000000000..fa6597274
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/DynamodbspringApplication.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/OrderService.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/OrderService.class
new file mode 100644
index 000000000..22080c89d
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/OrderService.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/config/AppConfig.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/config/AppConfig.class
new file mode 100644
index 000000000..b70ceee73
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/config/AppConfig.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Customer.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Customer.class
new file mode 100644
index 000000000..7d5c7f6f6
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Customer.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Order.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Order.class
new file mode 100644
index 000000000..133a4c32a
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Order.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/OrderID.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/OrderID.class
new file mode 100644
index 000000000..31656607c
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/OrderID.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Product.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Product.class
new file mode 100644
index 000000000..7cd742455
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/models/Product.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/repositories/CustomerRepository.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/repositories/CustomerRepository.class
new file mode 100644
index 000000000..28236afed
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/repositories/CustomerRepository.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/repositories/OrderRepository.class b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/repositories/OrderRepository.class
new file mode 100644
index 000000000..0accc9cb1
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/classes/io/pratik/dynamodbspring/repositories/OrderRepository.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/dynamodbspringdata-0.0.1-SNAPSHOT.jar b/aws/springdynamodb/dynamodbspringdata/target/dynamodbspringdata-0.0.1-SNAPSHOT.jar
new file mode 100644
index 000000000..699f0102d
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/dynamodbspringdata-0.0.1-SNAPSHOT.jar differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/maven-archiver/pom.properties b/aws/springdynamodb/dynamodbspringdata/target/maven-archiver/pom.properties
new file mode 100644
index 000000000..977782b52
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/target/maven-archiver/pom.properties
@@ -0,0 +1,5 @@
+#Generated by Maven
+#Thu Jun 03 12:13:08 GST 2021
+groupId=io.pratik
+artifactId=dynamodbspringdata
+version=0.0.1-SNAPSHOT
diff --git a/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 000000000..b5b855bc4
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,10 @@
+io/pratik/dynamodbspring/repositories/OrderRepository.class
+io/pratik/dynamodbspring/repositories/CustomerRepository.class
+io/pratik/dynamodbspring/CustomerService.class
+io/pratik/dynamodbspring/models/Product.class
+io/pratik/dynamodbspring/DynamodbspringApplication.class
+io/pratik/dynamodbspring/models/Order.class
+io/pratik/dynamodbspring/models/OrderID.class
+io/pratik/dynamodbspring/config/AppConfig.class
+io/pratik/dynamodbspring/OrderService.class
+io/pratik/dynamodbspring/models/Customer.class
diff --git a/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 000000000..04f2f608e
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,10 @@
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Order.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Product.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/OrderService.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/OrderID.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/config/AppConfig.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/models/Customer.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/DynamodbspringApplication.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/CustomerRepository.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/repositories/OrderRepository.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/main/java/io/pratik/dynamodbspring/CustomerService.java
diff --git a/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
new file mode 100644
index 000000000..295a13b85
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
@@ -0,0 +1,2 @@
+io/pratik/dynamodbspring/DynamodbspringApplicationTests.class
+io/pratik/dynamodbspring/CustomerServiceTest.class
diff --git a/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
new file mode 100644
index 000000000..252dc2ade
--- /dev/null
+++ b/aws/springdynamodb/dynamodbspringdata/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
@@ -0,0 +1,2 @@
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/CustomerServiceTest.java
+/Users/pratikdas/pratik/code-examples/aws/aws-dynamodb/dynamodbspringdata/src/test/java/io/pratik/dynamodbspring/DynamodbspringApplicationTests.java
diff --git a/aws/springdynamodb/dynamodbspringdata/target/test-classes/io/pratik/dynamodbspring/CustomerServiceTest.class b/aws/springdynamodb/dynamodbspringdata/target/test-classes/io/pratik/dynamodbspring/CustomerServiceTest.class
new file mode 100644
index 000000000..69e3dcea6
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/test-classes/io/pratik/dynamodbspring/CustomerServiceTest.class differ
diff --git a/aws/springdynamodb/dynamodbspringdata/target/test-classes/io/pratik/dynamodbspring/DynamodbspringApplicationTests.class b/aws/springdynamodb/dynamodbspringdata/target/test-classes/io/pratik/dynamodbspring/DynamodbspringApplicationTests.class
new file mode 100644
index 000000000..55056ae1b
Binary files /dev/null and b/aws/springdynamodb/dynamodbspringdata/target/test-classes/io/pratik/dynamodbspring/DynamodbspringApplicationTests.class differ
diff --git a/aws/springdynamodb/pom.xml b/aws/springdynamodb/pom.xml
new file mode 100644
index 000000000..6f5c369f6
--- /dev/null
+++ b/aws/springdynamodb/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ io.pratik
+ dynamodbapp
+ 0.0.1-SNAPSHOT
+ pom
+
+ dynamodbspringdata
+ dynamodbec
+
+
\ No newline at end of file
diff --git a/aws/sqs/.mvn/wrapper/MavenWrapperDownloader.java b/aws/sqs/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/aws/sqs/.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/aws/sqs/.mvn/wrapper/maven-wrapper.jar b/aws/sqs/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/aws/sqs/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/aws/sqs/.mvn/wrapper/maven-wrapper.properties b/aws/sqs/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/aws/sqs/.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/aws/sqs/README.md b/aws/sqs/README.md
new file mode 100644
index 000000000..45483a223
--- /dev/null
+++ b/aws/sqs/README.md
@@ -0,0 +1,8 @@
+# Getting started with AWS SQS
+
+
+
+Blog posts about this topic:
+
+* [Getting started with AWS SQS](https://reflectoring.io/getting-started-with-aws-sqs/)
+
diff --git a/aws/sqs/mvnw b/aws/sqs/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/aws/sqs/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/aws/sqs/mvnw.cmd b/aws/sqs/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/aws/sqs/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/aws/sqs/pom.xml b/aws/sqs/pom.xml
new file mode 100644
index 000000000..519d1e993
--- /dev/null
+++ b/aws/sqs/pom.xml
@@ -0,0 +1,58 @@
+
+
+ 4.0.0
+
+ io.pratik
+ sqs
+ 1.0
+
+
+ UTF-8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+
+
+
+
+
+
+
+
+
+
+ software.amazon.awssdk
+ sqs
+
+
+
+ software.amazon.awssdk
+ sns
+
+
+
+
+
+
+
+ software.amazon.awssdk
+ bom
+ 2.17.116
+ pom
+ import
+
+
+
+
diff --git a/aws/sqs/resources/index.js b/aws/sqs/resources/index.js
new file mode 100644
index 000000000..63ddae359
--- /dev/null
+++ b/aws/sqs/resources/index.js
@@ -0,0 +1,7 @@
+exports.handler = async function(event, context) {
+ event.Records.forEach(record => {
+ const { body } = record;
+ console.log(body);
+ });
+ return {};
+}
\ No newline at end of file
diff --git a/aws/sqs/src/main/java/io/pratik/sqs/AppConfig.java b/aws/sqs/src/main/java/io/pratik/sqs/AppConfig.java
new file mode 100644
index 000000000..aba1b094f
--- /dev/null
+++ b/aws/sqs/src/main/java/io/pratik/sqs/AppConfig.java
@@ -0,0 +1,14 @@
+/**
+ *
+ */
+package io.pratik.sqs;
+
+/**
+ * @author pratikdas
+ *
+ */
+public interface AppConfig {
+
+ String ACCOUNT_NO = "*********";
+
+}
diff --git a/aws/sqs/src/main/java/io/pratik/sqs/MessageReceiver.java b/aws/sqs/src/main/java/io/pratik/sqs/MessageReceiver.java
new file mode 100644
index 000000000..99fe074c4
--- /dev/null
+++ b/aws/sqs/src/main/java/io/pratik/sqs/MessageReceiver.java
@@ -0,0 +1,86 @@
+/**
+ *
+ */
+package io.pratik.sqs;
+
+import java.util.List;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sqs.SqsClient;
+import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
+import software.amazon.awssdk.services.sqs.model.DeleteMessageResponse;
+import software.amazon.awssdk.services.sqs.model.Message;
+import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class MessageReceiver {
+
+ /**
+ * @param args
+ * @throws InterruptedException
+ */
+ public static void main(String[] args) throws InterruptedException {
+ // receiveMessage();
+ receiveFifoMessage();
+
+ }
+
+ public static void receiveMessage() {
+ SqsClient sqsClient = getSQSClient();
+ final String queueURL = "https://sqs.us-east-1.amazonaws.com/" +AppConfig.ACCOUNT_NO + "/myqueue";
+
+ // long polling and wait for waitTimeSeconds before timed out
+ ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
+ .queueUrl(queueURL)
+ .waitTimeSeconds(20)
+ .messageAttributeNames("trace-id") // returns the trace Id
+ .build();
+ List messages = sqsClient.receiveMessage(receiveMessageRequest).messages();
+ }
+
+ public static void receiveFifoMessage() throws InterruptedException {
+ SqsClient sqsClient = getSQSClient();
+ final String queueURL = "https://sqs.us-east-1.amazonaws.com/" +AppConfig.ACCOUNT_NO + "/myfifoqueue.fifo";
+
+ // long polling and wait for waitTimeSeconds before timed out
+ ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
+ .queueUrl(queueURL)
+ .waitTimeSeconds(20)
+ .messageAttributeNames("trace-id") // returns the trace Id
+ .build();
+
+ while(true) {
+
+ Thread.sleep(20000l);
+ List messages = sqsClient.receiveMessage(receiveMessageRequest).messages();
+ messages.stream().forEach(msg->{
+ System.out.println(msg.body() + " " + msg.attributesAsStrings());
+
+ String receiptHandle = msg.receiptHandle();
+ DeleteMessageRequest deleteMessageRequest = DeleteMessageRequest.builder().queueUrl(queueURL).receiptHandle(receiptHandle).build();
+ DeleteMessageResponse deleteMessageResponse = sqsClient.deleteMessage(deleteMessageRequest );
+
+ // logger.info(deleteMessageResponse.responseMetadata());
+ });
+
+ }
+
+
+ }
+
+ private static SqsClient getSQSClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create("pratikpoc");
+
+ SqsClient sqsClient = SqsClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return sqsClient;
+ }
+
+}
diff --git a/aws/sqs/src/main/java/io/pratik/sqs/MessageSender.java b/aws/sqs/src/main/java/io/pratik/sqs/MessageSender.java
new file mode 100644
index 000000000..80ef12877
--- /dev/null
+++ b/aws/sqs/src/main/java/io/pratik/sqs/MessageSender.java
@@ -0,0 +1,147 @@
+/**
+ *
+ */
+package io.pratik.sqs;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sns.SnsClient;
+import software.amazon.awssdk.services.sns.model.PublishRequest;
+import software.amazon.awssdk.services.sns.model.PublishResponse;
+import software.amazon.awssdk.services.sqs.SqsClient;
+import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
+import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
+import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class MessageSender {
+
+ private static final String TRACE_ID_NAME = "trace-id";
+ private static Logger logger = Logger.getLogger(MessageSender.class.getName());
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ // sendMessage();
+ // sendMessageToFifo();
+ sendMessageToSnsTopic();
+ }
+ public static void sendMessageToSnsTopic() {
+ SnsClient snsClient = getSNSClient();
+
+ Map messageAttributes = new HashMap();
+ // generates a UUID as the traceId
+ String traceId = UUID.randomUUID().toString();
+ // add traceId as a message attribute
+ messageAttributes.put(TRACE_ID_NAME, MessageAttributeValue.builder().dataType("String").stringValue(traceId).build());
+ final String topicArn = "arn:aws:sns:us-east-1:" + AppConfig.ACCOUNT_NO + ":mytopic";
+
+ PublishRequest publishRequest = PublishRequest.builder().topicArn(topicArn).message("Test message published to topic").build();
+ PublishResponse publishResponse = snsClient.publish(publishRequest);
+
+ logger.info("message id: "+ publishResponse.messageId());
+
+ snsClient.close();
+ }
+
+
+ public static void sendMessage() {
+ SqsClient sqsClient = getSQSClient();
+
+ Map messageAttributes = new HashMap();
+ // generates a UUID as the traceId
+ String traceId = UUID.randomUUID().toString();
+ // add traceId as a message attribute
+ messageAttributes.put(TRACE_ID_NAME, MessageAttributeValue.builder().dataType("String").stringValue(traceId).build());
+
+ final String queueURL = "https://sqs.us-east-1.amazonaws.com/" +AppConfig.ACCOUNT_NO + "/myqueue";
+ SendMessageRequest sendMessageRequest = SendMessageRequest.builder().queueUrl(queueURL).messageBody("Test message")
+ .messageAttributes(messageAttributes)
+ .build();
+ SendMessageResponse sendMessageResponse = sqsClient.sendMessage(sendMessageRequest);
+
+ logger.info("message id: "+ sendMessageResponse.messageId() );
+
+ sqsClient.close();
+ }
+
+ public static void sendMessageToFifo() {
+ SqsClient sqsClient = getSQSClient();
+
+ Map messageAttributes = new HashMap();
+ // generates a UUID as the traceId
+ String traceId = UUID.randomUUID().toString();
+ // add traceId as a message attribute
+ messageAttributes.put(TRACE_ID_NAME, MessageAttributeValue.builder().dataType("String").stringValue(traceId).build());
+
+ final String queueURL = "https://sqs.us-east-1.amazonaws.com/" +AppConfig.ACCOUNT_NO + "/myfifoqueue.fifo";
+
+
+ List dedupIds = List.of("dedupid1","dedupid2","dedupid3","dedupid2","dedupid1");
+
+ String messageGroupId = "signup";
+
+ List messages = List.of(
+ "My fifo message1",
+ "My fifo message2",
+ "My fifo message3",
+ "My fifo message2",
+ "My fifo message1");
+ short loop = 0;
+ for (String message : messages) {
+
+ SendMessageRequest sendMessageRequest = SendMessageRequest.builder()
+ .queueUrl(queueURL)
+ .messageBody(message)
+ .messageAttributes(messageAttributes)
+ .messageDeduplicationId(dedupIds.get(loop))
+ .messageGroupId(messageGroupId)
+ .build();
+
+ SendMessageResponse sendMessageResponse = sqsClient
+ .sendMessage(sendMessageRequest);
+
+ logger.info("message id and sequence no.: "+ sendMessageResponse.messageId() + " | " + sendMessageResponse.sequenceNumber());
+
+ logger.info("responseMetadata " + sendMessageResponse.responseMetadata());
+ loop+=1;
+ }
+
+
+ sqsClient.close();
+ }
+
+
+
+ private static SqsClient getSQSClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create("pratikpoc");
+
+ SqsClient sqsClient = SqsClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return sqsClient;
+ }
+
+ private static SnsClient getSNSClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create("pratikpoc");
+
+ SnsClient snsClient = SnsClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return snsClient;
+ }
+
+}
diff --git a/aws/sqs/src/main/java/io/pratik/sqs/ResourceHelper.java b/aws/sqs/src/main/java/io/pratik/sqs/ResourceHelper.java
new file mode 100644
index 000000000..ec3efcf00
--- /dev/null
+++ b/aws/sqs/src/main/java/io/pratik/sqs/ResourceHelper.java
@@ -0,0 +1,138 @@
+/**
+ *
+ */
+package io.pratik.sqs;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sns.SnsClient;
+import software.amazon.awssdk.services.sns.model.CreateTopicRequest;
+import software.amazon.awssdk.services.sns.model.CreateTopicResponse;
+import software.amazon.awssdk.services.sns.model.SubscribeRequest;
+import software.amazon.awssdk.services.sns.model.SubscribeResponse;
+import software.amazon.awssdk.services.sqs.SqsClient;
+import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;
+import software.amazon.awssdk.services.sqs.model.CreateQueueResponse;
+import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class ResourceHelper {
+ private static Logger logger = Logger.getLogger(ResourceHelper.class.getName());
+
+ public static void main(String[] args) {
+ // createStandardQueue();
+ // createFifoQueue();
+ createSNSTopicWithSubscription();
+ }
+
+
+
+ public static void createStandardQueue() {
+ SqsClient sqsClient = getSQSClient();
+
+ String dlqName = "mydlq";
+ CreateQueueRequest createQueueRequest = CreateQueueRequest.builder()
+ .queueName(dlqName)
+ .build();
+
+
+ // Create dead letter queue
+ CreateQueueResponse createQueueResponse = sqsClient.createQueue(createQueueRequest);
+
+
+ String dlqArn = getQueueArn(dlqName,"us-east-1");
+
+ Map attributeMap = new HashMap();
+ attributeMap.put(QueueAttributeName.REDRIVE_POLICY,
+ "{\"maxReceiveCount\":10,\"deadLetterTargetArn\":\""+dlqArn+"\"}");
+
+ // Prepare request for creating the standard queue
+ createQueueRequest = CreateQueueRequest.builder()
+ .queueName("myqueue")
+ .attributes(attributeMap)
+ .build();
+
+ // create the queue
+ createQueueResponse = sqsClient.createQueue(createQueueRequest);
+
+ logger.info("Queue URL " + createQueueResponse.queueUrl());
+ }
+
+ public static void createFifoQueue() {
+ SqsClient sqsClient = getSQSClient();
+
+
+ Map attributeMap = new HashMap();
+
+ attributeMap.put(QueueAttributeName.FIFO_QUEUE, "true");
+ attributeMap.put(QueueAttributeName.DEDUPLICATION_SCOPE, "messageGroup");
+ attributeMap.put(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, "false");
+
+ CreateQueueRequest createQueueRequest = CreateQueueRequest.builder()
+ .queueName("myfifoqueue.fifo")
+ .attributes(attributeMap )
+ .build();
+
+ CreateQueueResponse createQueueResponse = sqsClient.createQueue(createQueueRequest);
+ logger.info("url "+createQueueResponse.queueUrl());
+ }
+
+ public static void createSNSTopicWithSubscription() {
+ SnsClient snsClient = getSNSClient();
+
+ CreateTopicRequest createTopicRequest = CreateTopicRequest.builder().name("mytopic").build();
+ CreateTopicResponse createTopicResponse = snsClient.createTopic(createTopicRequest );
+
+ String topicArn = createTopicResponse.topicArn();
+ //Topic topic = Topic.builder().topicArn(topicArn).build();
+
+ String queueArn= getQueueArn("myqueue","us-east-1");
+
+ SubscribeRequest subscribeRequest = SubscribeRequest.builder()
+ .protocol("sqs")
+ .topicArn(topicArn)
+ .endpoint(queueArn)
+ .build();
+ SubscribeResponse subscribeResponse = snsClient.subscribe(subscribeRequest );
+
+
+ logger.info("subscriptionArn " + subscribeResponse.subscriptionArn());
+ }
+
+
+
+ private static SqsClient getSQSClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create("pratikpoc");
+
+ SqsClient sqsClient = SqsClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return sqsClient;
+ }
+
+ private static SnsClient getSNSClient() {
+ AwsCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create("pratikpoc");
+
+ SnsClient snsClient = SnsClient
+ .builder()
+ .credentialsProvider(credentialsProvider)
+ .region(Region.US_EAST_1).build();
+ return snsClient;
+ }
+
+
+
+ private static String getQueueArn(final String queueName, final String region) {
+ return "arn:aws:sqs:"+region + ":" + AppConfig.ACCOUNT_NO+ ":" + queueName;
+ }
+
+}
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 9d22e1560..3c7e03954 100755
--- a/build-all.sh
+++ b/build-all.sh
@@ -1,5 +1,6 @@
#!/bin/bash
+MODULE=$1
MAIN_DIR=$PWD
build_gradle_module() {
@@ -10,7 +11,7 @@ build_gradle_module() {
echo "+++"
cd $MODULE_PATH && {
chmod +x gradlew
- ./gradlew clean build
+ ./gradlew build
if [ $? -ne 0 ]
then
echo ""
@@ -63,7 +64,7 @@ build_maven_module() {
echo "+++"
cd $MODULE_PATH && {
chmod +x mvnw
- ./mvnw clean package
+ ./mvnw package
if [ $? -ne 0 ]
then
echo ""
@@ -81,54 +82,184 @@ build_maven_module() {
}
}
-run_gradle_task "spring-boot/thymeleaf-vue" "npmInstall"
-build_gradle_module "spring-boot/thymeleaf-vue"
-build_gradle_module "spring-boot/spring-boot-springdoc"
-build_maven_module "spring-boot/dependency-injection"
-build_maven_module "spring-boot/spring-boot-openapi"
-build_maven_module "spring-boot/data-migration/liquibase"
-build_gradle_module "spring-boot/boundaries"
-build_gradle_module "spring-boot/argumentresolver"
-build_gradle_module "spring-data/spring-data-jdbc-converter"
-build_gradle_module "solid"
-build_gradle_module "spring-boot/data-migration/flyway"
-build_gradle_module "reactive"
-build_gradle_module "junit/assumptions"
-build_gradle_module "logging"
-build_gradle_module "pact/pact-feign-consumer"
-# currently disabled since the consumer build won't run
-# build_gradle_module "pact/pact-message-consumer"
-# build_gradle_module "pact/pact-message-producer"
-build_gradle_module "pact/pact-spring-provider"
-build_gradle_module "patterns"
-build_gradle_module "spring-boot/conditionals"
-build_gradle_module "spring-boot/configuration"
-build_gradle_module "spring-boot/mocking"
-build_gradle_module "spring-boot/modular"
-build_gradle_module "spring-boot/paging"
-build_gradle_module "spring-boot/rabbitmq-event-brokering"
-build_gradle_module "spring-boot/spring-boot-logging"
-build_gradle_module "spring-boot/spring-boot-testing"
-build_gradle_module "spring-boot/starter"
-build_gradle_module "spring-boot/startup"
-build_gradle_module "spring-boot/static"
-build_gradle_module "spring-boot/validation"
-build_gradle_module "spring-boot/profiles"
-build_gradle_module "spring-boot/password-encoding"
-build_gradle_module "spring-boot/testcontainers"
-build_gradle_module "spring-boot/hazelcast/hazelcast-embedded-cache"
-build_gradle_module "spring-boot/hazelcast/hazelcast-client-server"
-build_gradle_module "spring-boot/cache"
-build_gradle_module "spring-cloud/feign-with-spring-data-rest"
-build_gradle_module "spring-cloud/sleuth-downstream-service"
-build_gradle_module "spring-cloud/sleuth-upstream-service"
-build_gradle_module "spring-cloud/spring-cloud-contract-consumer"
-build_gradle_module "spring-cloud/spring-cloud-contract-provider"
-build_gradle_module "spring-data/spring-data-rest-associations"
-build_gradle_module "spring-data/spring-data-rest-springfox"
-build_gradle_module "tools/jacoco"
-
-echo ""
-echo "+++"
-echo "+++ ALL MODULES SUCCESSFUL"
-echo "+++"
+
+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"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 7 SUCCESSFUL"
+ echo "+++"
+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"
+ build maven_module "core-java/collectionops"
+ build maven_module "spring-boot/resttemplate"
+ build_maven_module "spring-cloud/tracing"
+ build_maven_module "core-java/versions"
+ build maven_module "java-hashes"
+ build maven_module "http-clients"
+ build maven_module "spring-boot/spring-boot-i18n"
+ build_maven_module "testing/assertJ"
+ build maven_module "spring-boot/spring-boot-scheduler"
+ build maven_module "aws/springcloudwatch"
+ build maven_module "aws/springcloudses"
+ build maven_module "spring-boot/spring-boot-camel"
+ build_maven_module "logging/structured-logging"
+ build_maven_module "spring-boot/zero-downtime"
+ build_maven_module "resilience4j/springboot-resilience4j"
+ build_maven_module "spring-boot/feature-flags"
+ build_gradle_module "aws/spring-cloud-caching-redis"
+ build_maven_module "logging/spring-boot"
+ build_maven_module "logging/logback"
+ build_maven_module "logging/log4j"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 6 SUCCESSFUL"
+ echo "+++"
+fi
+
+if [[ "$MODULE" == "module5" ]]
+then
+ build_maven_module "spring-boot/beginners-guide"
+ build_maven_module "aws/aws-dynamodb"
+ build_maven_module "spring-boot/spring-boot-testconfiguration"
+ build_maven_module "aws/springcloudrds"
+ build_maven_module "aws/springcloudsqs"
+ build_maven_module "spring-boot/spring-boot-actuator"
+ build_maven_module "mockito"
+ build_maven_module "core-java/service-provider-interface"
+ build_gradle_module "spring-boot/hazelcast/hazelcast-embedded-cache"
+ build_gradle_module "spring-boot/hazelcast/hazelcast-client-server"
+ build_maven_module "core-java/heapdump"
+ build_gradle_module "aws/s3"
+ build_maven_module "graphql"
+ build_gradle_module "spring-boot/exception-handling"
+ build_maven_module "spring-boot/spring-boot-elasticsearch"
+ build_gradle_module "spring-boot/spring-boot-mocking-modules"
+ build_gradle_module "spring-boot/specification"
+ build_gradle_module "spring-boot/hibernate-search"
+ build_maven_module "core-java/streams/fileswithstreams"
+ build_maven_module "spring-boot/spring-boot-health-check"
+ build_maven_module "spring-boot/spring-boot-logging-2"
+ build_maven_module "spring-boot/spring-boot-docker"
+ build_maven_module "spring-boot/spring-component-scanning"
+ build_gradle_module "spring-boot/devtools-demo"
+ build_gradle_module "spring-boot/cache"
+ build_gradle_module "spring-boot/bean-lifecycle"
+ build_gradle_module "spring-boot/request-response/client"
+ build_gradle_module "spring-boot/request-response/server"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 5 SUCCESSFUL"
+ echo "+++"
+fi
+
+
+if [[ "$MODULE" == "module1" ]]
+then
+ build_maven_module "spring-boot/spring-boot-cookie-demo"
+ build_maven_module "spring-boot/spring-boot-kafka"
+ build_gradle_module "spring-boot/spring-boot-springdoc"
+ build_maven_module "spring-boot/dependency-injection"
+ build_maven_module "spring-boot/spring-boot-openapi"
+ build_maven_module "spring-boot/data-migration/liquibase"
+ build_gradle_module "spring-boot/boundaries"
+ build_gradle_module "spring-boot/argumentresolver"
+ build_gradle_module "spring-boot/data-migration/flyway"
+ run_gradle_task "spring-boot/thymeleaf-vue" "clean npmInstall build"
+ build_gradle_module "spring-boot/conditionals"
+ build_gradle_module "spring-boot/configuration"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 1 SUCCESSFUL"
+ echo "+++"
+fi
+
+if [[ "$MODULE" == "module2" ]]
+then
+ build_gradle_module "solid/isp"
+ build_maven_module "solid/lsp"
+ build_maven_module "resilience4j/retry"
+ build_maven_module "resilience4j/ratelimiter"
+ 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"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 2 SUCCESSFUL"
+ echo "+++"
+fi
+
+if [[ "$MODULE" == "module3" ]]
+then
+ build_maven_module "aws/localstack"
+ build_gradle_module "pact/pact-spring-provider"
+ build_gradle_module "patterns"
+ build_gradle_module "spring-cloud/feign-with-spring-data-rest"
+ build_gradle_module "spring-cloud/sleuth-downstream-service"
+ build_gradle_module "spring-cloud/sleuth-upstream-service"
+ build_gradle_module "spring-cloud/spring-cloud-contract-provider" # has to run before consumer
+ build_gradle_module "spring-cloud/spring-cloud-contract-consumer"
+ build_gradle_module "spring-data/spring-data-rest-associations"
+ build_gradle_module "spring-data/spring-data-rest-springfox"
+ build_gradle_module "tools/jacoco"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 3 SUCCESSFUL"
+ echo "+++"
+fi
+
+if [[ "$MODULE" == "module4" ]]
+then
+ build_gradle_module "spring-boot/mocking"
+ build_gradle_module "spring-boot/modular"
+ build_gradle_module "spring-boot/paging"
+ build_gradle_module "spring-boot/rabbitmq-event-brokering"
+ build_gradle_module "spring-boot/spring-boot-logging"
+ build_gradle_module "spring-boot/spring-boot-testing"
+ build_gradle_module "spring-boot/starter"
+ build_gradle_module "spring-boot/startup"
+ build_gradle_module "spring-boot/static"
+ build_gradle_module "spring-boot/validation"
+ build_gradle_module "spring-boot/profiles"
+ build_gradle_module "spring-boot/password-encoding"
+ build_gradle_module "spring-boot/testcontainers"
+
+ echo ""
+ echo "+++"
+ echo "+++ MODULE 4 SUCCESSFUL"
+ echo "+++"
+fi
diff --git a/core-java/annotation-processing/introduction-to-annotations/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/annotation-processing/introduction-to-annotations/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/.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/annotation-processing/introduction-to-annotations/.mvn/wrapper/maven-wrapper.jar b/core-java/annotation-processing/introduction-to-annotations/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/annotation-processing/introduction-to-annotations/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/annotation-processing/introduction-to-annotations/.mvn/wrapper/maven-wrapper.properties b/core-java/annotation-processing/introduction-to-annotations/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/.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/annotation-processing/introduction-to-annotations/README.md b/core-java/annotation-processing/introduction-to-annotations/README.md
new file mode 100644
index 000000000..c39850536
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/README.md
@@ -0,0 +1,3 @@
+# Introduction to Annotation Processing
+
+Blog article: [An Introduction to Annotations and Annotation Processing in Java](https://reflectoring.io/java-annotation-processing)
diff --git a/core-java/annotation-processing/introduction-to-annotations/mvnw b/core-java/annotation-processing/introduction-to-annotations/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/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/annotation-processing/introduction-to-annotations/mvnw.cmd b/core-java/annotation-processing/introduction-to-annotations/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/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/annotation-processing/introduction-to-annotations/pom.xml b/core-java/annotation-processing/introduction-to-annotations/pom.xml
new file mode 100644
index 000000000..c76727fe9
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+ org.example
+ annotations-demo
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+
+
+
\ No newline at end of file
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/AnnotatedMethods.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/AnnotatedMethods.java
new file mode 100644
index 000000000..9828f7eb4
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/AnnotatedMethods.java
@@ -0,0 +1,15 @@
+package com.reflectoring;
+
+public class AnnotatedMethods {
+
+ @Test
+ public void Test_1() {
+
+ System.out.println("This is the first test");
+ }
+
+ public void Test_2() {
+
+ System.out.println("This is the second test");
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/BasicAnnotationTest.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/BasicAnnotationTest.java
new file mode 100644
index 000000000..d3a1d3124
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/BasicAnnotationTest.java
@@ -0,0 +1,8 @@
+package com.reflectoring;
+
+public class BasicAnnotationTest {
+
+ public static void main(String[] args) {
+
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CSV.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CSV.java
new file mode 100644
index 000000000..9781cf4c0
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CSV.java
@@ -0,0 +1,11 @@
+package com.reflectoring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CSV {
+}
\ No newline at end of file
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/ClassRetention.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/ClassRetention.java
new file mode 100644
index 000000000..c6f2aec15
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/ClassRetention.java
@@ -0,0 +1,11 @@
+package com.reflectoring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ClassRetention {
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Company.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Company.java
new file mode 100644
index 000000000..07ce3bc43
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Company.java
@@ -0,0 +1,12 @@
+package com.reflectoring;
+
+import java.lang.annotation.*;
+
+@Inherited
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Company{
+ String name() default "ABC";
+ String city() default "XYZ";
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CustomAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CustomAnnotatedEmployee.java
new file mode 100644
index 000000000..a73f8d053
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CustomAnnotatedEmployee.java
@@ -0,0 +1,19 @@
+package com.reflectoring;
+
+@Company
+public class CustomAnnotatedEmployee {
+
+ private int id;
+ private String name;
+
+ public CustomAnnotatedEmployee(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public void getEmployeeDetails(){
+
+ System.out.println("Employee Id: " + id);
+ System.out.println("Employee Name: " + name);
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CustomAnnotatedManager.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CustomAnnotatedManager.java
new file mode 100644
index 000000000..540a0e8f8
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/CustomAnnotatedManager.java
@@ -0,0 +1,8 @@
+package com.reflectoring;
+
+public class CustomAnnotatedManager extends CustomAnnotatedEmployee{
+
+ public CustomAnnotatedManager(int id, String name) {
+ super(id, name);
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/DeprecatedDemo.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/DeprecatedDemo.java
new file mode 100644
index 000000000..37588fa09
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/DeprecatedDemo.java
@@ -0,0 +1,10 @@
+package com.reflectoring;
+
+public class DeprecatedDemo {
+
+ @Deprecated(since = "4.5", forRemoval = true)
+ public void testLegacyFunction() {
+
+ System.out.println("This is a legacy function");
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/DeprecatedDemoTest.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/DeprecatedDemoTest.java
new file mode 100644
index 000000000..7065c1706
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/DeprecatedDemoTest.java
@@ -0,0 +1,10 @@
+package com.reflectoring;
+
+public class DeprecatedDemoTest {
+
+ public static void main(String[] args) {
+
+ DeprecatedDemo demo = new DeprecatedDemo();
+ demo.testLegacyFunction();
+ }
+}
\ No newline at end of file
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Employee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Employee.java
new file mode 100644
index 000000000..bc015d96d
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Employee.java
@@ -0,0 +1,10 @@
+package com.reflectoring;
+
+@Company
+public class Employee {
+
+ public void getEmployeeStatus(){
+
+ System.out.println("This is the Base Employee class");
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/EmployeeRetentionAnnotation.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/EmployeeRetentionAnnotation.java
new file mode 100644
index 000000000..f9badd672
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/EmployeeRetentionAnnotation.java
@@ -0,0 +1,7 @@
+package com.reflectoring;
+
+@SourceRetention
+@RuntimeRetention
+@ClassRetention
+public class EmployeeRetentionAnnotation {
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/FunctionalInterfaceTest.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/FunctionalInterfaceTest.java
new file mode 100644
index 000000000..20ab61579
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/FunctionalInterfaceTest.java
@@ -0,0 +1,15 @@
+package com.reflectoring;
+
+@FunctionalInterface
+interface Print {
+ void printString(String testString);
+}
+
+public class FunctionalInterfaceTest {
+
+ public static void main(String args[]) {
+
+ Print testPrint = (String testString) -> System.out.println(testString);
+ testPrint.printString("This is a String");
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Manager.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Manager.java
new file mode 100644
index 000000000..27b20e5a6
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Manager.java
@@ -0,0 +1,10 @@
+package com.reflectoring;
+
+public class Manager extends Employee {
+
+ @Override
+ public void getEmployeeStatus(){
+
+ System.out.println("This is the Manager class");
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/MultiValueAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/MultiValueAnnotatedEmployee.java
new file mode 100644
index 000000000..a0d8a6c4d
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/MultiValueAnnotatedEmployee.java
@@ -0,0 +1,6 @@
+package com.reflectoring;
+
+@Company(name = "AAA", city = "ZZZ")
+public class MultiValueAnnotatedEmployee {
+
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/OverrideTest.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/OverrideTest.java
new file mode 100644
index 000000000..4c5626fff
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/OverrideTest.java
@@ -0,0 +1,10 @@
+package com.reflectoring;
+
+public class OverrideTest {
+
+ public static void main(String[] args) {
+
+ Manager manager = new Manager();
+ manager.getEmployeeStatus();
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatableCompanies.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatableCompanies.java
new file mode 100644
index 000000000..2237a7399
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatableCompanies.java
@@ -0,0 +1,9 @@
+package com.reflectoring;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RepeatableCompanies {
+ RepeatableCompany[] value() default{};
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatableCompany.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatableCompany.java
new file mode 100644
index 000000000..d0b8e3583
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatableCompany.java
@@ -0,0 +1,11 @@
+package com.reflectoring;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Repeatable(RepeatableCompanies.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RepeatableCompany {
+ String name() default "Name_1";
+ String city() default "City_1";
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatedAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatedAnnotatedEmployee.java
new file mode 100644
index 000000000..ff8acb11c
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RepeatedAnnotatedEmployee.java
@@ -0,0 +1,6 @@
+package com.reflectoring;
+
+@RepeatableCompany
+@RepeatableCompany(name = "Name_2", city = "City_2")
+public class RepeatedAnnotatedEmployee {
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RetentionTest.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RetentionTest.java
new file mode 100644
index 000000000..a2a4b1447
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RetentionTest.java
@@ -0,0 +1,16 @@
+package com.reflectoring;
+
+public class RetentionTest {
+
+ public static void main(String[] args) {
+
+ SourceRetention[] sourceRetention = new EmployeeRetentionAnnotation().getClass().getAnnotationsByType(SourceRetention.class);
+ System.out.println("Source Retentions at run-time: " + sourceRetention.length);
+
+ RuntimeRetention[] runtimeRetention = new EmployeeRetentionAnnotation().getClass().getAnnotationsByType(RuntimeRetention.class);
+ System.out.println("Run-time Retentions at run-time: " + runtimeRetention.length);
+
+ ClassRetention[] classRetention = new EmployeeRetentionAnnotation().getClass().getAnnotationsByType(ClassRetention.class);
+ System.out.println("Class Retentions at run-time: " + classRetention.length);
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RuntimeRetention.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RuntimeRetention.java
new file mode 100644
index 000000000..d6063af2f
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/RuntimeRetention.java
@@ -0,0 +1,11 @@
+package com.reflectoring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RuntimeRetention {
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SafeVarargsTest.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SafeVarargsTest.java
new file mode 100644
index 000000000..727325032
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SafeVarargsTest.java
@@ -0,0 +1,57 @@
+package com.reflectoring;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class SafeVarargsTest {
+
+ private void printString(String test1) {
+
+ System.out.println(test1);
+ }
+
+ private void printString(String test1, String test2) {
+
+ System.out.println(test1);
+ System.out.println(test2);
+ }
+
+ private void printStringVarargs(String... tests) {
+
+ for (String test : tests) {
+
+ System.out.println(test);
+ }
+ }
+
+ @SafeVarargs
+ private void printStringSafeVarargs(List... testStringLists) {
+
+ for (List testStringList : testStringLists) {
+
+ for (String testString : testStringList) {
+
+ System.out.println(testString);
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+
+ SafeVarargsTest test = new SafeVarargsTest();
+
+ test.printString("String1");
+ test.printString("*******");
+
+ test.printString("String1", "String2");
+ test.printString("*******");
+
+ test.printStringVarargs("String1", "String2");
+ test.printString("*******");
+
+ List testStringList1 = Arrays.asList("One", "Two");
+ List testStringList2 = Arrays.asList("Three", "Four");
+
+ test.printStringSafeVarargs(testStringList1, testStringList2);
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SingleValueAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SingleValueAnnotatedEmployee.java
new file mode 100644
index 000000000..b480b9d06
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SingleValueAnnotatedEmployee.java
@@ -0,0 +1,19 @@
+package com.reflectoring;
+
+@SingleValueAnnotationCompany("XYZ")
+public class SingleValueAnnotatedEmployee {
+
+ private int id;
+ private String name;
+
+ public SingleValueAnnotatedEmployee(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public void getEmployeeDetails(){
+
+ System.out.println("Employee Id: " + id);
+ System.out.println("Employee Name: " + name);
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SingleValueAnnotationCompany.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SingleValueAnnotationCompany.java
new file mode 100644
index 000000000..2d249357a
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SingleValueAnnotationCompany.java
@@ -0,0 +1,9 @@
+package com.reflectoring;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SingleValueAnnotationCompany {
+ String value() default "ABC";
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SourceRetention.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SourceRetention.java
new file mode 100644
index 000000000..a120ac0f0
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SourceRetention.java
@@ -0,0 +1,8 @@
+package com.reflectoring;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface SourceRetention {
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SuppressWarningsDemo.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SuppressWarningsDemo.java
new file mode 100644
index 000000000..1a81f1b93
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/SuppressWarningsDemo.java
@@ -0,0 +1,22 @@
+package com.reflectoring;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class SuppressWarningsDemo {
+
+ public static void main(String[] args) {
+
+ SuppressWarningsDemo swDemo = new SuppressWarningsDemo();
+ swDemo.testSuppressWarning();
+ }
+
+ public void testSuppressWarning() {
+
+ Map testMap = new HashMap();
+ testMap.put(1, "Item_1");
+ testMap.put(2, "Item_2");
+ testMap.put(3, "Item_3");
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Test.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Test.java
new file mode 100644
index 000000000..111e8e981
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/Test.java
@@ -0,0 +1,11 @@
+package com.reflectoring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Test {
+}
\ No newline at end of file
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestAnnotatedMethods.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestAnnotatedMethods.java
new file mode 100644
index 000000000..ba44ea803
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestAnnotatedMethods.java
@@ -0,0 +1,31 @@
+package com.reflectoring;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+public class TestAnnotatedMethods {
+
+ public static void main(String[] args) throws Exception {
+
+ Class annotatedMethodsClass = AnnotatedMethods.class;
+
+ for (Method method : annotatedMethodsClass.getDeclaredMethods()) {
+
+ Annotation annotation = method.getAnnotation(Test.class);
+ Test test = (Test) annotation;
+
+ // If the annotation is not null
+ if (test != null) {
+
+ try {
+ method.invoke(annotatedMethodsClass.getDeclaredConstructor().newInstance());
+ } catch (Throwable ex) {
+ System.out.println(ex.getCause());
+ }
+
+ }
+ }
+ }
+}
+
+
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestCustomAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestCustomAnnotatedEmployee.java
new file mode 100644
index 000000000..24bc2c6f3
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestCustomAnnotatedEmployee.java
@@ -0,0 +1,18 @@
+package com.reflectoring;
+
+import java.lang.annotation.Annotation;
+
+public class TestCustomAnnotatedEmployee {
+
+ public static void main(String[] args) {
+
+ CustomAnnotatedEmployee employee = new CustomAnnotatedEmployee(1, "John Doe");
+ employee.getEmployeeDetails();
+
+ Annotation companyAnnotation = employee.getClass().getAnnotation(Company.class);
+ Company company = (Company)companyAnnotation;
+
+ System.out.println("Company Name: " + company.name());
+ System.out.println("Company City: " + company.city());
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestCustomAnnotatedManager.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestCustomAnnotatedManager.java
new file mode 100644
index 000000000..34d56bdf8
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestCustomAnnotatedManager.java
@@ -0,0 +1,18 @@
+package com.reflectoring;
+
+import java.lang.annotation.Annotation;
+
+public class TestCustomAnnotatedManager {
+
+ public static void main(String[] args) {
+
+ CustomAnnotatedManager manager = new CustomAnnotatedManager(1, "John Doe");
+ manager.getEmployeeDetails();
+
+ Annotation companyAnnotation = manager.getClass().getAnnotation(Company.class);
+ Company company = (Company)companyAnnotation;
+
+ System.out.println("Company Name: " + company.name());
+ System.out.println("Company City: " + company.city());
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestMarkerAnnotation.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestMarkerAnnotation.java
new file mode 100644
index 000000000..01f9bc568
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestMarkerAnnotation.java
@@ -0,0 +1,16 @@
+package com.reflectoring;
+
+public class TestMarkerAnnotation {
+
+ public static void main(String[] args) {
+
+ XYZClient client = new XYZClient();
+ Class clientClass = client.getClass();
+
+ if (clientClass.isAnnotationPresent(CSV.class)){
+ System.out.println("Write client data to CSV.");
+ } else {
+ System.out.println("Write client data to Excel file.");
+ }
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestMultiValueAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestMultiValueAnnotatedEmployee.java
new file mode 100644
index 000000000..f5fb3743b
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestMultiValueAnnotatedEmployee.java
@@ -0,0 +1,17 @@
+package com.reflectoring;
+
+import java.lang.annotation.Annotation;
+
+public class TestMultiValueAnnotatedEmployee {
+
+ public static void main(String[] args) {
+
+ MultiValueAnnotatedEmployee employee = new MultiValueAnnotatedEmployee();
+
+ Annotation companyAnnotation = employee.getClass().getAnnotation(Company.class);
+ Company company = (Company)companyAnnotation;
+
+ System.out.println("Company Name: " + company.name());
+ System.out.println("Company City: " + company.city());
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestRepeatedAnnotation.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestRepeatedAnnotation.java
new file mode 100644
index 000000000..2948d0c4e
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestRepeatedAnnotation.java
@@ -0,0 +1,14 @@
+package com.reflectoring;
+
+public class TestRepeatedAnnotation {
+
+ public static void main(String[] args) {
+
+ RepeatableCompany[] repeatableCompanies = RepeatedAnnotatedEmployee.class.getAnnotationsByType(RepeatableCompany.class);
+ for (RepeatableCompany repeatableCompany : repeatableCompanies) {
+
+ System.out.println("Name: " + repeatableCompany.name());
+ System.out.println("City: " + repeatableCompany.city());
+ }
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestSingleValueAnnotatedEmployee.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestSingleValueAnnotatedEmployee.java
new file mode 100644
index 000000000..7449b3d84
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/TestSingleValueAnnotatedEmployee.java
@@ -0,0 +1,17 @@
+package com.reflectoring;
+
+import java.lang.annotation.Annotation;
+
+public class TestSingleValueAnnotatedEmployee {
+
+ public static void main(String[] args) {
+
+ SingleValueAnnotatedEmployee employee = new SingleValueAnnotatedEmployee(1, "John Doe");
+ employee.getEmployeeDetails();
+
+ Annotation companyAnnotation = employee.getClass().getAnnotation(SingleValueAnnotationCompany.class);
+ SingleValueAnnotationCompany company = (SingleValueAnnotationCompany)companyAnnotation;
+
+ System.out.println("Company Name: " + company.value());
+ }
+}
diff --git a/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/XYZClient.java b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/XYZClient.java
new file mode 100644
index 000000000..a84d07c8d
--- /dev/null
+++ b/core-java/annotation-processing/introduction-to-annotations/src/main/java/com/reflectoring/XYZClient.java
@@ -0,0 +1,6 @@
+package com.reflectoring;
+
+@CSV
+public class XYZClient {
+
+}
\ No newline at end of file
diff --git a/core-java/collectionops/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/collectionops/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/collectionops/.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/collectionops/.mvn/wrapper/maven-wrapper.jar b/core-java/collectionops/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/collectionops/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/collectionops/.mvn/wrapper/maven-wrapper.properties b/core-java/collectionops/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/collectionops/.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/collectionops/README.md b/core-java/collectionops/README.md
new file mode 100644
index 000000000..ffca4ff76
--- /dev/null
+++ b/core-java/collectionops/README.md
@@ -0,0 +1,3 @@
+# Related Blog Posts
+
+* [Logical Operations Between Java Collections](https://reflectoring.io/logical-ops-on-java-collections/)
diff --git a/core-java/collectionops/mvnw b/core-java/collectionops/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/collectionops/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/collectionops/mvnw.cmd b/core-java/collectionops/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/collectionops/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/collectionops/pom.xml b/core-java/collectionops/pom.xml
new file mode 100644
index 000000000..22758b46d
--- /dev/null
+++ b/core-java/collectionops/pom.xml
@@ -0,0 +1,24 @@
+
+ 4.0.0
+ io.pratik
+ logicalops
+ 0.0.1-SNAPSHOT
+
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.4
+
+
+ com.google.guava
+ guava
+ 31.0.1-jre
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/collectionops/src/main/java/io/pratik/CollectionHelper.java b/core-java/collectionops/src/main/java/io/pratik/CollectionHelper.java
new file mode 100644
index 000000000..62357c090
--- /dev/null
+++ b/core-java/collectionops/src/main/java/io/pratik/CollectionHelper.java
@@ -0,0 +1,135 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import com.google.common.collect.Lists;
+
+/**
+ * @author pratikdas
+ *
+ */
+public class CollectionHelper {
+
+ public List[] split(List listToSplit){
+ // determine the endpoints to use in `list.subList()` method
+ int[] endpoints = {0, (listToSplit.size() + 1)/2, listToSplit.size()};
+
+
+ List> sublists =
+ IntStream.rangeClosed(0, 1)
+ .mapToObj(i -> listToSplit.subList(endpoints[i], endpoints[i + 1]))
+ .collect(Collectors.toList());
+
+ // return an array containing both lists
+ return new List[] {sublists.get(0), sublists.get(1)};
+ }
+
+ public List add(final List collA, final List collB){
+
+ return Stream.concat(collA.stream(),
+ collB.stream())
+ .collect(Collectors.toList());
+
+
+ }
+
+ public List addWithFilter(final List collA, final List collB){
+
+ return Stream.concat(collA.stream(),
+ collB.stream())
+ .filter(element -> element > 2 )
+ .collect(Collectors.toList());
+ }
+
+ public List union(final List collA, final List collB){
+ Set set = new LinkedHashSet<>();
+ set.addAll(collA);
+ set.addAll(collB);
+
+ return new ArrayList<>(set);
+
+ }
+
+ public List intersection(final List collA, final List collB){
+ List intersectElements = collA.stream()
+ .filter(collB :: contains)
+ .collect(Collectors.toList());
+
+ if(!intersectElements.isEmpty()) {
+ return intersectElements;
+ }else {
+ return Collections.emptyList();
+ }
+
+ }
+
+ public Collection> partition(final List collA, final int chunkSize){
+ final AtomicInteger counter = new AtomicInteger();
+
+ final Collection> result = collA.stream()
+ .collect(Collectors.groupingBy(it -> counter.getAndIncrement() / chunkSize))
+ .values();
+
+ return result;
+
+ }
+
+
+ public List removeDuplicates(final List collA){
+ List listWithoutDuplicates = new ArrayList<>(
+ new LinkedHashSet<>(collA));
+
+ return listWithoutDuplicates;
+ }
+
+ public List xor(final List collA, final List collB){
+
+ List listOfAnotInB = collA.stream().filter(element->{
+ return !collB.contains(element);
+ }).collect(Collectors.toList());
+
+ List listOfBnotInA = collB.stream().filter(element->{
+ return !collA.contains(element);
+ }).collect(Collectors.toList());
+
+ return Stream.concat(listOfAnotInB.stream(),
+ listOfBnotInA.stream())
+ .collect(Collectors.toList());
+ }
+
+ public List not(final List collA, final List collB){
+
+ List notList = collA.stream().filter(element->{
+ return !collB.contains(element);
+ }).collect(Collectors.toList());
+
+ return notList;
+ }
+
+ public List subtract(final List collA, final List collB){
+ List intersectElements = intersection(collA,collB);
+
+ List subtractedElements = collA.stream().filter(element->!intersectElements.contains(element)).collect(Collectors.toList());
+
+ if(!subtractedElements.isEmpty()) {
+ return subtractedElements;
+ }else {
+ return Collections.emptyList();
+ }
+
+ }
+
+}
diff --git a/core-java/collectionops/src/test/java/io/pratik/tests/CollectionHelperTest.java b/core-java/collectionops/src/test/java/io/pratik/tests/CollectionHelperTest.java
new file mode 100644
index 000000000..a906d4e12
--- /dev/null
+++ b/core-java/collectionops/src/test/java/io/pratik/tests/CollectionHelperTest.java
@@ -0,0 +1,178 @@
+/**
+ *
+ */
+package io.pratik.tests;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.pratik.CollectionHelper;
+
+/**
+ * @author pratikdas
+ *
+ */
+class CollectionHelperTest {
+
+ private CollectionHelper collectionHelper;
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @BeforeEach
+ void setUp() throws Exception {
+ collectionHelper = new CollectionHelper();
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @AfterEach
+ void tearDown() throws Exception {
+ }
+
+ @Test
+ void testUnion() {
+ List union = collectionHelper.union(
+ List.of(9, 8, 5, 4, 7),
+ List.of(1, 3, 99, 4, 7));
+
+
+ Assertions.assertArrayEquals(
+ List.of(9, 8, 5, 4, 7, 1, 3, 99).toArray(),
+ union.toArray());
+
+ }
+
+ @Test
+ void testIntersection() {
+ List intersection = collectionHelper.intersection(
+ List.of(9,8,5,4,7, 15, 15),
+ List.of(1,3,99,4,7));
+
+ Assertions.assertArrayEquals(
+ List.of(4,7).toArray(),
+ intersection.toArray());
+ }
+
+ @Test
+ void testXOR() {
+ List xorList = collectionHelper.xor(
+ List.of(9, 8, 5, 4, 7),
+ List.of(1, 99, 4, 7));
+
+ Assertions.assertArrayEquals(
+ List.of(9, 8, 5, 1, 99).toArray(),
+ xorList.toArray());
+ }
+
+ @Test
+ void testNOT() {
+ List xorList = collectionHelper.not(
+ List.of(9,8,5,4,7),
+ List.of(1,99,4,7));
+
+ Assertions.assertArrayEquals(
+ List.of(9,8,5).toArray(),
+ xorList.toArray());
+ }
+
+ @Test
+ void testAddition() {
+ List sub = collectionHelper.add(
+ List.of(9,8,5,4),
+ List.of(1,3,99,4,7));
+
+
+ Assertions.assertArrayEquals(
+ List.of(9,8,5,4,1,3,99,4,7).toArray(),
+ sub.toArray());
+ }
+
+ @Test
+ void testAdditionWithFilter() {
+ List list = collectionHelper.addWithFilter(
+ List.of(9,8,5,4),
+ List.of(1,3,99,4,7));
+
+
+ Assertions.assertArrayEquals(
+ List.of(9,8,5,4,3,99,4,7).toArray(),
+ list.toArray());
+ }
+
+
+ @Test
+ void testSubtraction() {
+ List sub = collectionHelper.subtract(
+ List.of(9,8,5,4,7, 15, 15),
+ List.of(1,3,99,4,7));
+
+
+ Assertions.assertArrayEquals(
+ List.of(9,8,5,15,15).toArray(),
+ sub.toArray());
+ }
+
+ @Test
+ void testPartition() {
+ Collection> partitions = collectionHelper.partition(
+ List.of(9, 8, 5, 4, 7, 15, 15), 2);
+
+ Iterator> iter = partitions.iterator();
+
+ int loop = 0;
+ while(iter.hasNext()) {
+ List element = iter.next();
+ System.out.println(element);
+ if(loop == 0)
+ assertArrayEquals(List.of(9, 8).toArray(),element.toArray());
+ else if(loop == 1)
+ assertArrayEquals(List.of(5, 4).toArray(),element.toArray());
+ else if(loop == 2)
+ assertArrayEquals(List.of(7, 15).toArray(),element.toArray());
+ else if(loop == 3)
+ assertArrayEquals(List.of(15).toArray(),element.toArray());
+
+ ++loop;
+ }
+
+
+ }
+
+ @Test
+ void testRemoveDuplicates() {
+ List uniqueElements = collectionHelper.removeDuplicates(
+ List.of(9, 8, 5, 4, 4, 7, 15, 15));
+
+
+
+ Assertions.assertArrayEquals(
+ List.of(9, 8, 5, 4, 7, 15).toArray(),
+ uniqueElements.toArray());
+ }
+
+ @Test
+ void testSplit() {
+ List[] subLists = collectionHelper.split(
+ List.of(9, 8, 5, 4, 7, 15, 15));
+
+
+ Assertions.assertArrayEquals(
+ List.of(9,8,5,4).toArray(),
+ subLists[0].toArray());
+
+ Assertions.assertArrayEquals(
+ List.of(7,15,15).toArray(),
+ subLists[1].toArray());
+ }
+
+}
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/heapdump/.DS_Store b/core-java/heapdump/.DS_Store
new file mode 100644
index 000000000..d99c736e1
Binary files /dev/null and b/core-java/heapdump/.DS_Store differ
diff --git a/core-java/heapdump/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/heapdump/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/heapdump/.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/heapdump/.mvn/wrapper/maven-wrapper.jar b/core-java/heapdump/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/heapdump/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/heapdump/.mvn/wrapper/maven-wrapper.properties b/core-java/heapdump/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/heapdump/.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/heapdump/README.md b/core-java/heapdump/README.md
new file mode 100644
index 000000000..6b3142395
--- /dev/null
+++ b/core-java/heapdump/README.md
@@ -0,0 +1,3 @@
+# Related Blog Posts
+
+* [How to do heap dump analysis](https://reflectoring.io/create-analyze-heapdump/)
diff --git a/core-java/heapdump/mvnw b/core-java/heapdump/mvnw
new file mode 100755
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/heapdump/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/heapdump/mvnw.cmd b/core-java/heapdump/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/heapdump/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/heapdump/pom.xml b/core-java/heapdump/pom.xml
new file mode 100644
index 000000000..36093f282
--- /dev/null
+++ b/core-java/heapdump/pom.xml
@@ -0,0 +1,32 @@
+
+ 4.0.0
+ io.pratik
+ oomegen
+ 0.0.1-SNAPSHOT
+ oomegen
+
+ 11
+ 11
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.4
+
+
+
+ io.pratik.OOMGenerator
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/heapdump/src/.DS_Store b/core-java/heapdump/src/.DS_Store
new file mode 100644
index 000000000..5008ddfcf
Binary files /dev/null and b/core-java/heapdump/src/.DS_Store differ
diff --git a/core-java/heapdump/src/main/java/io/pratik/AbstractProduct.java b/core-java/heapdump/src/main/java/io/pratik/AbstractProduct.java
new file mode 100644
index 000000000..15836714c
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/AbstractProduct.java
@@ -0,0 +1,54 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class AbstractProduct {
+
+ private String name;
+ private String id;
+ private byte[] logo;
+ private static final Random random = new Random();
+
+ public AbstractProduct() {
+ super();
+ this.id = UUID.randomUUID().toString();
+ this.logo = new byte[(5 ) * 1024 * 1024];
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public byte[] getLogo() {
+ return logo;
+ }
+
+ public void setLogo(byte[] logo) {
+ this.logo = logo;
+ }
+
+ public long getSize() {
+ return JavaAgent.getObjectSize(this) + JavaAgent.getObjectSize(logo) + JavaAgent.getObjectSize(id);
+ }
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/JavaAgent.java b/core-java/heapdump/src/main/java/io/pratik/JavaAgent.java
new file mode 100644
index 000000000..47503d928
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/JavaAgent.java
@@ -0,0 +1,23 @@
+/**
+ *
+ */
+package io.pratik;
+
+import java.lang.instrument.Instrumentation;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class JavaAgent {
+ private static volatile Instrumentation instrumentation;
+ public static void premain(final String agentArgs, final Instrumentation instrumentation) {
+ JavaAgent.instrumentation = instrumentation;
+ }
+ public static long getObjectSize(final Object object) {
+ if (instrumentation == null) {
+ return -1L;
+ }
+ return instrumentation.getObjectSize(object);
+ }
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/OOMGenerator.java b/core-java/heapdump/src/main/java/io/pratik/OOMGenerator.java
new file mode 100644
index 000000000..ecb81825f
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/OOMGenerator.java
@@ -0,0 +1,29 @@
+/**
+ *
+ */
+package io.pratik;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class OOMGenerator {
+
+ /**
+ * @param args
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+
+ System.out.println("Max JVM memory: " + Runtime.getRuntime().maxMemory()/(1024*1024));
+ try {
+ ProductManager productManager = new ProductManager();
+ productManager.populateProducts();
+
+ } catch (OutOfMemoryError outofMemory) {
+ System.out.println("Catching out of memory error "+ Runtime.getRuntime().freeMemory()/(1024*1024));
+ // Log the information,so that we can generate the statistics (latter on).
+ throw outofMemory;
+ }
+ }
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/ProductManager.java b/core-java/heapdump/src/main/java/io/pratik/ProductManager.java
new file mode 100644
index 000000000..9fa637b21
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/ProductManager.java
@@ -0,0 +1,58 @@
+/**
+ *
+ */
+package io.pratik;
+
+import io.pratik.models.BrandedProduct;
+import io.pratik.models.ElectronicGood;
+import io.pratik.models.GroceryProduct;
+import io.pratik.models.LuxuryGood;
+import io.pratik.models.ProductGroup;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class ProductManager {
+ private ProductGroup regularItems = new ProductGroup();
+
+ private ProductGroup discountedItems = new ProductGroup();
+
+ public void populateProducts() {
+
+ int dummyArraySize = 1;
+ for (int loop = 0; loop < 10; loop++) {
+ if(loop%2 == 0) {
+ createObjects(regularItems, dummyArraySize);
+ }else {
+ createObjects(discountedItems, dummyArraySize);
+ }
+ System.out.println("Memory Consumed till now: " + loop + "::"+ regularItems + " "+discountedItems );
+ dummyArraySize *= dummyArraySize * 2;
+ }
+ }
+
+ private void createObjects(ProductGroup productGroup, int dummyArraySize) {
+ for (int i = 0; i < dummyArraySize; ) {
+ productGroup.add(createProduct());
+ }
+ }
+
+ private AbstractProduct createProduct() {
+ int randomIndex = (int) Math.round(Math.random() * 10);
+ switch (randomIndex) {
+ case 0:
+ return new ElectronicGood();
+ case 1:
+ return new BrandedProduct();
+ case 2:
+ return new GroceryProduct();
+ case 3:
+ return new LuxuryGood();
+ default:
+ return new BrandedProduct();
+ }
+
+ }
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/BrandedProduct.java b/core-java/heapdump/src/main/java/io/pratik/models/BrandedProduct.java
new file mode 100644
index 000000000..d5dc256c9
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/BrandedProduct.java
@@ -0,0 +1,48 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.pratik.AbstractProduct;
+import io.pratik.JavaAgent;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class BrandedProduct extends AbstractProduct{
+ private String brandName;
+ private Price mrp;
+
+
+
+ public BrandedProduct() {
+ super();
+ mrp = new Price();
+
+ }
+
+ public Price getMrp() {
+ return mrp;
+ }
+
+ public void setMrp(Price mrp) {
+ this.mrp = mrp;
+ }
+
+ public String getBrandName() {
+ return brandName;
+ }
+
+ public void setBrandName(String brandName) {
+ this.brandName = brandName;
+ }
+
+ public long getSize() {
+ return JavaAgent.getObjectSize(this) + JavaAgent.getObjectSize(brandName);
+ }
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/ElectronicGood.java b/core-java/heapdump/src/main/java/io/pratik/models/ElectronicGood.java
new file mode 100644
index 000000000..fdb089ac8
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/ElectronicGood.java
@@ -0,0 +1,31 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import io.pratik.AbstractProduct;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class ElectronicGood extends AbstractProduct{
+
+ private Manufacturer manufacturer;
+
+
+
+ public ElectronicGood() {
+ super();
+ this.manufacturer = new Manufacturer();
+ }
+
+ public Manufacturer getManufacturer() {
+ return manufacturer;
+ }
+
+ public void setManufacturer(Manufacturer manufacturer) {
+ this.manufacturer = manufacturer;
+ }
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/GroceryProduct.java b/core-java/heapdump/src/main/java/io/pratik/models/GroceryProduct.java
new file mode 100644
index 000000000..0afb3eaad
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/GroceryProduct.java
@@ -0,0 +1,35 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import io.pratik.AbstractProduct;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class GroceryProduct extends AbstractProduct{
+ private Price mrp;
+ private Price discountedPrice;
+
+
+ public GroceryProduct() {
+ super();
+ mrp = new Price();
+ discountedPrice = new Price();
+ }
+ public Price getMrp() {
+ return mrp;
+ }
+ public void setMrp(Price mrp) {
+ this.mrp = mrp;
+ }
+ public Price getDiscountedPrice() {
+ return discountedPrice;
+ }
+ public void setDiscountedPrice(Price discountedPrice) {
+ this.discountedPrice = discountedPrice;
+ }
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/LuxuryGood.java b/core-java/heapdump/src/main/java/io/pratik/models/LuxuryGood.java
new file mode 100644
index 000000000..f83f219a3
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/LuxuryGood.java
@@ -0,0 +1,25 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import io.pratik.AbstractProduct;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class LuxuryGood extends AbstractProduct{
+ private String luxuryCategory;
+
+ public String getLuxuryCategory() {
+ return luxuryCategory;
+ }
+
+ public void setLuxuryCategory(String luxuryCategory) {
+ this.luxuryCategory = luxuryCategory;
+ }
+
+
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/Manufacturer.java b/core-java/heapdump/src/main/java/io/pratik/models/Manufacturer.java
new file mode 100644
index 000000000..6195418ee
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/Manufacturer.java
@@ -0,0 +1,43 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import io.pratik.JavaAgent;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class Manufacturer {
+
+ private String name;
+ private String address;
+
+
+ public Manufacturer() {
+ super();
+ this.name = "Dummy";
+ this.address = "dummy state";
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getAddress() {
+ return address;
+ }
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public long getSize() {
+ return JavaAgent.getObjectSize(this)
+ + JavaAgent.getObjectSize(name)
+ + JavaAgent.getObjectSize(address);
+ }
+
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/Price.java b/core-java/heapdump/src/main/java/io/pratik/models/Price.java
new file mode 100644
index 000000000..9ff0125f2
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/Price.java
@@ -0,0 +1,45 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import java.util.Date;
+
+import io.pratik.JavaAgent;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public final class Price {
+
+ private Double value;
+ private Date lastUpdated;
+
+
+ public Price() {
+ super();
+ value = 48.0;
+ lastUpdated = new Date();
+
+ }
+ public Double getValue() {
+ return value;
+ }
+ public void setValue(Double value) {
+ this.value = value;
+ }
+ public Date getLastUpdated() {
+ return lastUpdated;
+ }
+ public void setLastUpdated(Date lastUpdated) {
+ this.lastUpdated = lastUpdated;
+ }
+
+ public long getSize() {
+ return JavaAgent.getObjectSize(this)
+ + JavaAgent.getObjectSize(value)
+ + JavaAgent.getObjectSize(lastUpdated.toString());
+ }
+
+}
diff --git a/core-java/heapdump/src/main/java/io/pratik/models/ProductGroup.java b/core-java/heapdump/src/main/java/io/pratik/models/ProductGroup.java
new file mode 100644
index 000000000..02c35c8be
--- /dev/null
+++ b/core-java/heapdump/src/main/java/io/pratik/models/ProductGroup.java
@@ -0,0 +1,22 @@
+/**
+ *
+ */
+package io.pratik.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.pratik.AbstractProduct;
+
+/**
+ * @author Pratik Das
+ *
+ */
+public class ProductGroup {
+
+ private List products = new ArrayList();
+
+ public void add(AbstractProduct product) {
+ products.add(product);
+ }
+}
diff --git a/core-java/heapdump/src/main/resources/META-INF/MANIFEST.MF b/core-java/heapdump/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..ab7def718
--- /dev/null
+++ b/core-java/heapdump/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Premain-Class: io.pratik.JavaAgent
\ No newline at end of file
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>() {}).getBody();
+ assertThat(apiResponse).isEqualTo(Map.of("status", "FAIL"));
+
+ }
+
+
+}
diff --git a/core-java/lombok/be-informed-with-lombok/src/test/resources/application-test.yaml b/core-java/lombok/be-informed-with-lombok/src/test/resources/application-test.yaml
new file mode 100644
index 000000000..e78fc081e
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/src/test/resources/application-test.yaml
@@ -0,0 +1,13 @@
+spring:
+ datasource:
+ driver-class-name: org.hsqldb.jdbc.JDBCDriver
+ url: jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1
+ username: sa
+ password:
+
+ jpa:
+ hibernate:
+ ddl-auto: create
+
+server:
+ port: 8022
\ No newline at end of file
diff --git a/core-java/lombok/be-informed-with-lombok/wrapper/MavenWrapperDownloader.java b/core-java/lombok/be-informed-with-lombok/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/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/wrapper/maven-wrapper.jar b/core-java/lombok/be-informed-with-lombok/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/lombok/be-informed-with-lombok/wrapper/maven-wrapper.jar differ
diff --git a/core-java/lombok/be-informed-with-lombok/wrapper/maven-wrapper.properties b/core-java/lombok/be-informed-with-lombok/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/lombok/be-informed-with-lombok/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/records/.gitignore b/core-java/records/.gitignore
new file mode 100644
index 000000000..3aff99610
--- /dev/null
+++ b/core-java/records/.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/records/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/records/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/records/.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/records/.mvn/wrapper/maven-wrapper.jar b/core-java/records/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/records/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/records/.mvn/wrapper/maven-wrapper.properties b/core-java/records/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/records/.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/records/README.md b/core-java/records/README.md
new file mode 100644
index 000000000..ae82b2723
--- /dev/null
+++ b/core-java/records/README.md
@@ -0,0 +1,3 @@
+# Related Blog Posts
+
+* [Use Cases for Java Records](https://reflectoring.io/beginner-friendly-guide-to-java-records/)
diff --git a/core-java/records/mvnw b/core-java/records/mvnw
new file mode 100644
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/records/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/records/mvnw.cmd b/core-java/records/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/records/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/records/pom.xml b/core-java/records/pom.xml
new file mode 100644
index 000000000..b3dfc4698
--- /dev/null
+++ b/core-java/records/pom.xml
@@ -0,0 +1,80 @@
+
+
+
+ 4.0.0
+
+ io.reflectoring
+ java.records
+ 1.0-SNAPSHOT
+
+ Java Records
+ https://reflectoring.io
+
+
+ UTF-8
+ 17
+ 17
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 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/records/src/main/java/io/reflectoring/records/Address.java b/core-java/records/src/main/java/io/reflectoring/records/Address.java
new file mode 100644
index 000000000..dfef59905
--- /dev/null
+++ b/core-java/records/src/main/java/io/reflectoring/records/Address.java
@@ -0,0 +1,5 @@
+package io.reflectoring.records;
+
+import java.util.List;
+
+public record Address(String city, List streets) {}
diff --git a/core-java/records/src/main/java/io/reflectoring/records/Person.java b/core-java/records/src/main/java/io/reflectoring/records/Person.java
new file mode 100644
index 000000000..8ef324f97
--- /dev/null
+++ b/core-java/records/src/main/java/io/reflectoring/records/Person.java
@@ -0,0 +1,59 @@
+package io.reflectoring.records;
+
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+
+public record Person(
+ @NotBlank String name,
+ @Min(value = 0, message = "Age cannot be lesser that 0")
+ @Max(value = 130, message = "Age cannot be greater that 130")
+ int age) {
+
+ private static final String DEFAULT_NAME = "Foo";
+ private static final int DEFAULT_AGE = 50;
+ private static final int MIN_AGE = 0;
+ private static final int ADULT_MIN_AGE = 18;
+ private static final int MAX_AGE = 130;
+
+ public Person {
+ if (name == null || name.isBlank()) {
+ throw new IllegalArgumentException("Got invalid name.");
+ }
+ if (age < MIN_AGE || age > MAX_AGE) {
+ throw new IllegalArgumentException("Got invalid age");
+ }
+ }
+
+ public Person() {
+ this(DEFAULT_NAME, DEFAULT_AGE);
+ }
+
+ public Person(String name) {
+ this(name, DEFAULT_AGE);
+ }
+
+ public boolean isNameValid() {
+ return !(name == null || name.isBlank());
+ }
+
+ public boolean isAgeValid() {
+ return age > MIN_AGE && age < MAX_AGE;
+ }
+
+ public boolean isAdult() {
+ return age >= ADULT_MIN_AGE;
+ }
+
+ public Person withInput(String name, int age) {
+ return new Person(name, age);
+ }
+
+ public Person withName(String name) {
+ return new Person(name, DEFAULT_AGE);
+ }
+
+ public Person withAge(int age) {
+ return new Person(DEFAULT_NAME, age);
+ }
+}
diff --git a/core-java/records/src/test/java/io/reflectoring/records/AddressTest.java b/core-java/records/src/test/java/io/reflectoring/records/AddressTest.java
new file mode 100644
index 000000000..50124997c
--- /dev/null
+++ b/core-java/records/src/test/java/io/reflectoring/records/AddressTest.java
@@ -0,0 +1,35 @@
+package io.reflectoring.records;
+
+import lombok.extern.slf4j.Slf4j;
+import org.assertj.core.api.Condition;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Slf4j
+class AddressTest {
+ @Test
+ void immutableCities() {
+ List streets = new ArrayList<>();
+ streets.add("Street 1");
+ streets.add("Street 2");
+ Condition stringCondition = new Condition<>(streets::contains, "Street 3");
+
+ Address address = new Address("City", List.copyOf(streets));
+ streets.add("Street 3");
+
+ log.info("Address {}", address);
+ assertThat(address)
+ .isNotNull()
+ .extracting(Address::streets)
+ .matches(
+ (Predicate super List>)
+ list -> list.containsAll(Arrays.asList("Street 1", "Street 2")),
+ "Should have Street 1 and 2");
+ }
+}
diff --git a/core-java/records/src/test/java/io/reflectoring/records/PersonTest.java b/core-java/records/src/test/java/io/reflectoring/records/PersonTest.java
new file mode 100644
index 000000000..3073f2c19
--- /dev/null
+++ b/core-java/records/src/test/java/io/reflectoring/records/PersonTest.java
@@ -0,0 +1,103 @@
+package io.reflectoring.records;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import lombok.extern.slf4j.Slf4j;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+class PersonTest {
+
+ @Test
+ void allArgConstructor() {
+ final Person bar = new Person("Bar", 40);
+ assertThat(bar)
+ .as("Person cannot be null.")
+ .isNotNull()
+ .hasToString("Person[name=Bar, age=40]");
+ log.info("Bar {}", bar);
+ }
+
+ @Test
+ void compactConstructor() {
+ final Person person = new Person();
+ assertThat(person)
+ .as("Person cannot be null.")
+ .isNotNull()
+ .hasToString("Person[name=Foo, age=50]");
+ log.info("Person {}", person);
+ }
+
+ @Test
+ void nameArgConstructor() {
+ final Person person = new Person("Tom");
+ assertThat(person)
+ .as("Person cannot be null.")
+ .isNotNull()
+ .hasToString("Person[name=Tom, age=50]");
+ log.info("Person {}", person);
+ }
+
+ @Test
+ void name() {
+ final Person person = new Person();
+ assertThat(person).as("Person cannot be null.").isNotNull().extracting("name").isEqualTo("Foo");
+ log.info("Person {}", person);
+ }
+
+ @Test
+ void age() {
+ final Person person = new Person();
+ assertThat(person).as("Person cannot be null.").isNotNull().extracting("age").isEqualTo(50);
+ log.info("Person {}", person);
+ }
+
+ @Test
+ void adult() {
+ final Person adultPerson = new Person("Tom", 20);
+ assertThat(adultPerson.isAdult()).as("Person should be adult.").isTrue();
+ log.info("Adult Person {}", adultPerson);
+
+ final Person minorPerson = new Person("Jerry", 10);
+ assertThat(minorPerson.isAdult()).as("Person should not be adult.").isFalse();
+ log.info("Minor Person {}", minorPerson);
+ }
+
+ @Test
+ void helperMethods() {
+ final Person fooPerson = new Person("Foo", 40);
+ final Person barPerson = new Person("Bar", 40);
+ SoftAssertions personBundle = new SoftAssertions();
+ personBundle.assertThat(fooPerson).isNotEqualTo(barPerson);
+ personBundle.assertThat(fooPerson.toString()).isNotEmpty();
+ personBundle.assertThat(fooPerson.hashCode()).isPositive();
+ personBundle.assertThat(barPerson.toString()).isNotEmpty();
+ personBundle.assertThat(barPerson.hashCode()).isPositive();
+ personBundle.assertAll();
+
+ log.info("Foo Person toString {} with hashcode {}", fooPerson, fooPerson.hashCode());
+ log.info("Bar Person toString {} with hashcode {}", barPerson, barPerson.hashCode());
+ }
+
+ @Test
+ void delegateMethod() {
+ final Person person = new Person();
+ final Person newPerson = person.withName("Tom");
+ assertThat(newPerson)
+ .as("Person cannot be null.")
+ .isNotNull()
+ .extracting("name", "age")
+ .containsExactly("Tom", 50);
+ log.info("Person {}", newPerson);
+ }
+
+ @Test
+ void invalidAge() {
+ final Throwable thrown = catchThrowable(() -> new Person("Bob", 132));
+ assertThat(thrown)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Got invalid age");
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/.gitignore b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.gitignore
new file mode 100644
index 000000000..744289df7
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.gitignore
@@ -0,0 +1,2 @@
+# Project exclude paths
+/target/
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/MavenWrapperDownloader.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..b901097f2
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.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/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/maven-wrapper.jar b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..2cc7d4a55
Binary files /dev/null and b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/maven-wrapper.properties b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/.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/retrofit/introduction-to-retrofit/AuditApplication/README.md b/core-java/retrofit/introduction-to-retrofit/AuditApplication/README.md
new file mode 100644
index 000000000..5d1286567
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/README.md
@@ -0,0 +1,39 @@
+# A simple Spring Boot Audit application
+This application manages auditing of the Library application
+
+## Details
+ * This application uses the in memory HSQLDB to call the Library application maintain audit logs of the calls made.
+ * It uses Spring Boot to create REST endpoints
+ * It uses Retrofit to make calls to the Library application
+ * It uses Maven and Java11 to build and run.
+
+## How to run
+ * Clone this project
+ * Use maven command: `mvn clean verify spring-boot:run`
+ * The application should run at `http://localhost:8081`
+ * Use POSTMAN to make REST calls
+
+## Sample request JSON body
+
+# POST Sample
+````json
+{
+"bookName": "The Da Vinci Code",
+"publisher": "Corgie Adult",
+"publicationYear": "2009",
+"isCopyrighted": true,
+"authors": [{
+"name": "Dan Brown",
+"dob" : "22/06/1964"
+}]
+}
+````
+
+# PUT Sample
+````json
+{
+ "bookName": "The Da Vinci Code",
+ "publisher": "Corgie Adult - 01",
+ "publicationYear": "2010"
+}
+````
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/checkstyle-suppressions.xml b/core-java/retrofit/introduction-to-retrofit/AuditApplication/checkstyle-suppressions.xml
new file mode 100644
index 000000000..63b9763a3
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/checkstyle-suppressions.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/checkstyle.xml b/core-java/retrofit/introduction-to-retrofit/AuditApplication/checkstyle.xml
new file mode 100644
index 000000000..d157d441b
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/checkstyle.xml
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/mvnw b/core-java/retrofit/introduction-to-retrofit/AuditApplication/mvnw
new file mode 100644
index 000000000..41c0f0c23
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/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/retrofit/introduction-to-retrofit/AuditApplication/mvnw.cmd b/core-java/retrofit/introduction-to-retrofit/AuditApplication/mvnw.cmd
new file mode 100644
index 000000000..86115719e
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/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/retrofit/introduction-to-retrofit/AuditApplication/pom.xml b/core-java/retrofit/introduction-to-retrofit/AuditApplication/pom.xml
new file mode 100644
index 000000000..d2894ceb0
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/pom.xml
@@ -0,0 +1,185 @@
+
+ 4.0.0
+ com.reflectoring
+ audit-app
+ 0.0.1-SNAPSHOT
+ audit-app
+
+ 11
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.3.2.RELEASE
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+ provided
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.mapstruct
+ mapstruct
+ 1.4.2.Final
+
+
+ org.hsqldb
+ hsqldb
+ 2.4.0
+ runtime
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
+ com.squareup.retrofit2
+ retrofit
+ 2.5.0
+
+
+ com.squareup.retrofit2
+ converter-jackson
+ 2.5.0
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ 3.4.0
+
+
+ com.squareup.retrofit2
+ retrofit-mock
+ 2.5.0
+ test
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.1.1
+
+ checkstyle.xml
+ checkstyle-suppressions.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
+ 3.8.0
+
+ 11
+ 11
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.4.2.Final
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/spotbugs-exclude.xml b/core-java/retrofit/introduction-to-retrofit/AuditApplication/spotbugs-exclude.xml
new file mode 100644
index 000000000..fff713695
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/spotbugs-exclude.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/Application.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/Application.java
new file mode 100644
index 000000000..f312583d7
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/Application.java
@@ -0,0 +1,19 @@
+package com.reflectoring.library;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ private static Logger log = LoggerFactory.getLogger(Application.class);
+
+ public static void main(String[] args) {
+
+ SpringApplication.run(Application.class, args);
+
+ }
+
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/client/LibraryClient.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/client/LibraryClient.java
new file mode 100644
index 000000000..56a3bf73b
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/client/LibraryClient.java
@@ -0,0 +1,30 @@
+package com.reflectoring.library.client;
+
+import com.reflectoring.library.model.LibResponse;
+import com.reflectoring.library.model.mapstruct.BookDto;
+import retrofit2.Call;
+import retrofit2.http.*;
+
+import java.util.List;
+
+public interface LibraryClient {
+
+ /*@Headers({
+ "Accept: application/json",
+ "Cache-Control: max-age=640000"
+ })*/
+ @GET("/library/managed/books")
+ Call> getAllBooks(@Query("type") String type);
+
+ @GET("/library/managed/books/{requestId}")
+ Call getAllBooksWithHeaders(@Header("requestId") String requestId);
+
+ @POST("/library/managed/books")
+ Call createNewBook(@Body BookDto book);
+
+ @PUT("/library/managed/books/{id}")
+ Call updateBook(@Path("id") Long id, @Body BookDto book);
+
+ @DELETE("/library/managed/books/{id}")
+ Call deleteBook(@Path("id") Long id);
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/config/ClientConfigProperties.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/config/ClientConfigProperties.java
new file mode 100644
index 000000000..c2954fac8
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/config/ClientConfigProperties.java
@@ -0,0 +1,21 @@
+package com.reflectoring.library.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "library")
+@Getter
+@Setter
+public class ClientConfigProperties {
+
+ private String endpoint;
+
+ private String username;
+
+ private String password;
+
+ private Integer connectionTimeout;
+
+ private Integer readWriteTimeout;
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/config/RestClientConfiguration.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/config/RestClientConfiguration.java
new file mode 100644
index 000000000..1bd2a7c64
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/config/RestClientConfiguration.java
@@ -0,0 +1,45 @@
+package com.reflectoring.library.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.reflectoring.library.client.LibraryClient;
+import com.reflectoring.library.interceptor.BasicAuthInterceptor;
+import com.reflectoring.library.interceptor.CacheInterceptor;
+import okhttp3.Cache;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import retrofit2.Retrofit;
+import retrofit2.converter.jackson.JacksonConverterFactory;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+@EnableConfigurationProperties(ClientConfigProperties.class)
+public class RestClientConfiguration {
+
+ @Bean
+ public LibraryClient libraryClient(ClientConfigProperties props) {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
+ interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
+ Cache cache = new Cache(new File("cache"), 10 * 1024 * 1024);
+ OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder()
+ .addInterceptor(new BasicAuthInterceptor(props.getUsername(), props.getPassword()))
+ .cache(cache)
+ .addNetworkInterceptor(new CacheInterceptor())
+ .addInterceptor(interceptor)
+ .connectTimeout(props.getConnectionTimeout(), TimeUnit.SECONDS)
+ .readTimeout(props.getReadWriteTimeout(), TimeUnit.SECONDS);
+
+ return new Retrofit.Builder().client(httpClientBuilder.build())
+ .baseUrl(props.getEndpoint())
+ .addConverterFactory(JacksonConverterFactory.create(new ObjectMapper()))
+ .build().create(LibraryClient.class);
+
+ }
+
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/interceptor/BasicAuthInterceptor.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/interceptor/BasicAuthInterceptor.java
new file mode 100644
index 000000000..def1ca916
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/interceptor/BasicAuthInterceptor.java
@@ -0,0 +1,26 @@
+package com.reflectoring.library.interceptor;
+
+import okhttp3.Credentials;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+public class BasicAuthInterceptor implements Interceptor {
+
+ private final String credentials;
+
+ public BasicAuthInterceptor(String user, String password) {
+ this.credentials = Credentials.basic(user, password);
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request request = chain.request();
+ Request authenticatedRequest = request.newBuilder()
+ .header("Authorization", credentials).build();
+ return chain.proceed(authenticatedRequest);
+ }
+
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/interceptor/CacheInterceptor.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/interceptor/CacheInterceptor.java
new file mode 100644
index 000000000..4850176ab
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/interceptor/CacheInterceptor.java
@@ -0,0 +1,25 @@
+package com.reflectoring.library.interceptor;
+
+import okhttp3.CacheControl;
+import okhttp3.Interceptor;
+import okhttp3.Response;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+public class CacheInterceptor implements Interceptor {
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Response response = chain.proceed(chain.request());
+
+ CacheControl cacheControl = new CacheControl.Builder()
+ .maxAge(1, TimeUnit.MINUTES) // 1 minutes cache
+ .build();
+
+ return response.newBuilder()
+ .removeHeader("Pragma")
+ .removeHeader("Cache-Control")
+ .header("Cache-Control", cacheControl.toString())
+ .build();
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/mapper/AuditMapper.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/mapper/AuditMapper.java
new file mode 100644
index 000000000..998c765fe
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/mapper/AuditMapper.java
@@ -0,0 +1,99 @@
+package com.reflectoring.library.mapper;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.reflectoring.library.model.LibResponse;
+import com.reflectoring.library.model.mapstruct.AuditDto;
+import com.reflectoring.library.model.mapstruct.BookDto;
+import com.reflectoring.library.util.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@Component
+public class AuditMapper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AuditMapper.class);
+
+ public AuditDto populateAuditLogForGet(List books) {
+ AuditDto log = null;
+ log = new AuditDto();
+ log.setId(UUID.randomUUID().toString());
+ log.setMethodType(HttpMethod.GET);
+ log.setRequest(null);
+ log.setResponse(String.valueOf(books.size()));
+ log.setSuccess(Constants.SUCCESS);
+ log.setTimestamp(LocalDateTime.now());
+ return log;
+
+ }
+
+ public AuditDto populateAuditLogForGetBook(BookDto book) {
+ AuditDto log = null;
+ log = new AuditDto();
+ log.setId(UUID.randomUUID().toString());
+ log.setMethodType(HttpMethod.GET);
+ log.setRequest(null);
+ log.setResponse(book.toString());
+ log.setSuccess(Constants.SUCCESS);
+ log.setTimestamp(LocalDateTime.now());
+ return log;
+
+ }
+
+ public AuditDto populateAuditLogForPostAndPut(BookDto book, LibResponse response, HttpMethod method) {
+ AuditDto log = null;
+ try {
+ log = new AuditDto();
+ log.setId(UUID.randomUUID().toString());
+ log.setMethodType(method);
+ log.setRequest(new ObjectMapper().writeValueAsString(book));
+ log.setResponse(new ObjectMapper().writeValueAsString(response));
+ log.setSuccess(Constants.SUCCESS);
+ log.setTimestamp(LocalDateTime.now());
+ } catch (JsonProcessingException ex) {
+ LOG.error("Error processing Object to json", ex);
+ }
+ return log;
+
+ }
+
+ public AuditDto populateAuditLogForDelete(Long id, LibResponse libResponse) {
+ AuditDto log = null;
+ try {
+ log = new AuditDto();
+ log.setId(UUID.randomUUID().toString());
+ log.setMethodType(HttpMethod.DELETE);
+ log.setRequest(String.valueOf(id));
+ log.setResponse(new ObjectMapper().writeValueAsString(libResponse));
+ log.setSuccess(Constants.SUCCESS);
+ log.setTimestamp(LocalDateTime.now());
+ } catch (JsonProcessingException ex) {
+ LOG.error("Error processing Object to json", ex);
+ }
+ return log;
+
+ }
+
+ public AuditDto populateAuditLogForException(String request, HttpMethod type, String errorResp) {
+ AuditDto log = null;
+ try {
+ log = new AuditDto();
+ log.setId(UUID.randomUUID().toString());
+ log.setMethodType(type);
+ log.setRequest(request);
+ log.setResponse(new ObjectMapper().writeValueAsString(errorResp));
+ log.setSuccess(Constants.ERROR);
+ log.setTimestamp(LocalDateTime.now());
+ } catch (JsonProcessingException ex) {
+ LOG.error("Error processing Object to json", ex);
+ }
+ return log;
+
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/mapper/LibraryMapper.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/mapper/LibraryMapper.java
new file mode 100644
index 000000000..b573e2626
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/mapper/LibraryMapper.java
@@ -0,0 +1,29 @@
+package com.reflectoring.library.mapper;
+
+import com.reflectoring.library.model.mapstruct.AuditDto;
+import com.reflectoring.library.model.mapstruct.AuthorDto;
+import com.reflectoring.library.model.mapstruct.BookDto;
+import com.reflectoring.library.model.persistence.AuditLog;
+import com.reflectoring.library.model.persistence.Author;
+import com.reflectoring.library.model.persistence.Book;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface LibraryMapper {
+ BookDto bookToBookDto(Book book);
+
+ List bookToBookDto(List book);
+
+ AuthorDto authorToAuthorDto(Author author);
+
+ Book bookDtoToBook(BookDto bookDto);
+
+ Author authorDtoToAuthor(AuthorDto authorDto);
+
+ AuditLog auditDtoToAuditLog(AuditDto auditDto);
+
+ AuditDto auditLogToAuditDto(AuditLog auditLog);
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/LibResponse.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/LibResponse.java
new file mode 100644
index 000000000..9e94de7d3
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/LibResponse.java
@@ -0,0 +1,16 @@
+package com.reflectoring.library.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class LibResponse {
+ private String responseCode;
+
+ private String responseMsg;
+}
\ No newline at end of file
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/Status.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/Status.java
new file mode 100644
index 000000000..f11fcdb43
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/Status.java
@@ -0,0 +1,31 @@
+package com.reflectoring.library.model;
+
+public enum Status {
+ SUCCESS(200) {
+ @Override
+ public String toString() {
+ return "Success";
+ }
+ },
+ ERROR(500) {
+ @Override
+ public String toString() {
+ return "Error";
+ }
+ };
+
+ private int code;
+
+ Status(int code) {
+ this.code = code;
+ }
+
+ public static int fetchCode(String statusCode) {
+ for (Status s : Status.values()) {
+ if (s.name().equals(statusCode.toUpperCase())) {
+ return s.code;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/AuditDto.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/AuditDto.java
new file mode 100644
index 000000000..db2259bf6
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/AuditDto.java
@@ -0,0 +1,39 @@
+package com.reflectoring.library.model.mapstruct;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.http.HttpMethod;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@ToString
+public class AuditDto {
+
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("request")
+ private String request;
+
+ @JsonProperty("response")
+ private String response;
+
+ @JsonProperty("methodType")
+ private HttpMethod methodType;
+
+ @JsonProperty("success")
+ private String success;
+
+ @JsonProperty("timestamp")
+ private LocalDateTime timestamp;
+
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/AuthorDto.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/AuthorDto.java
new file mode 100644
index 000000000..754292424
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/AuthorDto.java
@@ -0,0 +1,30 @@
+package com.reflectoring.library.model.mapstruct;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class AuthorDto {
+
+ @JsonProperty("id")
+ private long id;
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("dob")
+ private String dob;
+
+ @Override
+ public String toString() {
+ return "AuthorDto{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", dob='" + dob + '\'' +
+ '}';
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/BookDto.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/BookDto.java
new file mode 100644
index 000000000..0c4c83a48
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/mapstruct/BookDto.java
@@ -0,0 +1,43 @@
+package com.reflectoring.library.model.mapstruct;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Set;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class BookDto {
+ @JsonProperty("bookId")
+ private long id;
+
+ @JsonProperty("bookName")
+ private String name;
+
+ @JsonProperty("publisher")
+ private String publisher;
+
+ @JsonProperty("publicationYear")
+ private String publicationYear;
+
+ @JsonProperty("isCopyrighted")
+ private boolean copyrightIssued;
+
+ @JsonProperty("authors")
+ private Set authors;
+
+ @Override
+ public String toString() {
+ return "BookDto{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", publisher='" + publisher + '\'' +
+ ", publicationYear='" + publicationYear + '\'' +
+ ", copyrightIssued=" + copyrightIssued +
+ ", authors=" + authors +
+ '}';
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/AuditLog.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/AuditLog.java
new file mode 100644
index 000000000..0e1d46fdc
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/AuditLog.java
@@ -0,0 +1,34 @@
+package com.reflectoring.library.model.persistence;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.http.HttpMethod;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "AUDIT_LOG")
+@Getter
+@Setter
+public class AuditLog {
+
+ @Id
+ private String id;
+
+ private String request;
+
+ private String response;
+
+ @Enumerated(EnumType.ORDINAL)
+ private HttpMethod methodType;
+
+ private String success;
+
+ @Column(name = "CURR_TIMESTAMP")
+ private LocalDateTime timestamp;
+
+ public AuditLog() {
+
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/Author.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/Author.java
new file mode 100644
index 000000000..646bef716
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/Author.java
@@ -0,0 +1,69 @@
+package com.reflectoring.library.model.persistence;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Builder;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+@Entity
+public class Author implements Serializable {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long id;
+
+ private String name;
+
+ private String dob;
+
+ @ManyToMany(mappedBy = "authors")
+ private Set books;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getBooks() {
+ return books;
+ }
+
+ public void setBooks(Set books) {
+ this.books = books;
+ }
+
+ public String getDob() {
+ return dob;
+ }
+
+ public void setDob(String dob) {
+ this.dob = dob;
+ }
+
+ @Override
+ public String toString() {
+ return "Author{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", dob='" + dob + '\'' +
+ '}';
+ }
+}
diff --git a/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/Book.java b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/Book.java
new file mode 100644
index 000000000..94294916a
--- /dev/null
+++ b/core-java/retrofit/introduction-to-retrofit/AuditApplication/src/main/java/com/reflectoring/library/model/persistence/Book.java
@@ -0,0 +1,88 @@
+package com.reflectoring.library.model.persistence;
+
+import lombok.Builder;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.Set;
+
+@Entity
+@Table(name = "BOOK")
+public class Book implements Serializable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long id;
+
+ private String name;
+
+ private String publisher;
+
+ private String publicationYear;
+
+ private boolean copyrightIssued;
+
+ @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
+ @JoinTable(name = "author_book",
+ joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
+ inverseJoinColumns = @JoinColumn(name = "author_id", referencedColumnName = "id"))
+ private Set authors;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set