diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 591cc099d..4c674153d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,7 +17,7 @@ jobs:
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" ]
+ module: [ "module1", "module2", "module3", "module4", "module5", "module6", "module7" ]
steps:
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/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-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/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/pom.xml b/aws/localstack/pom.xml
index 26a8c9e40..bd8060870 100644
--- a/aws/localstack/pom.xml
+++ b/aws/localstack/pom.xml
@@ -65,6 +65,11 @@
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
diff --git a/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java
index 63108f0e3..3f0690801 100644
--- a/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java
+++ b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerImageStoreTest.java
@@ -12,6 +12,7 @@
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;
@@ -34,6 +35,7 @@
@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;
diff --git a/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java
index cd2902f1e..fbbbaac05 100644
--- a/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java
+++ b/aws/localstack/src/test/java/io/reflectoring/customerregistration/CustomerProfileStoreTest.java
@@ -10,6 +10,7 @@
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;
@@ -37,6 +38,7 @@
@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;
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/spring-cloud/sleuth-upstream-service/gradlew b/aws/s3/gradlew
similarity index 74%
rename from spring-cloud/sleuth-upstream-service/gradlew
rename to aws/s3/gradlew
index 4453ccea3..4f906e0c8 100755
--- a/spring-cloud/sleuth-upstream-service/gradlew
+++ b/aws/s3/gradlew
@@ -1,5 +1,21 @@
#!/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
@@ -28,16 +44,16 @@ 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=""
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -66,6 +82,7 @@ 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
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# 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
@@ -138,35 +156,30 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ 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" ;;
+ 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 ( ) {
+save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+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"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
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/spring-cloud/sleuth-upstream-service/gradle/wrapper/gradle-wrapper.properties b/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.properties
similarity index 92%
rename from spring-cloud/sleuth-upstream-service/gradle/wrapper/gradle-wrapper.properties
rename to aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.properties
index 38c1d48d1..442d9132e 100644
--- a/spring-cloud/sleuth-upstream-service/gradle/wrapper/gradle-wrapper.properties
+++ b/aws/spring-cloud-caching-redis/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +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
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
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 14443686f..3c7e03954 100755
--- a/build-all.sh
+++ b/build-all.sh
@@ -11,7 +11,7 @@ build_gradle_module() {
echo "+++"
cd $MODULE_PATH && {
chmod +x gradlew
- ./gradlew clean build
+ ./gradlew build
if [ $? -ne 0 ]
then
echo ""
@@ -64,7 +64,7 @@ build_maven_module() {
echo "+++"
cd $MODULE_PATH && {
chmod +x mvnw
- ./mvnw clean package
+ ./mvnw package
if [ $? -ne 0 ]
then
echo ""
@@ -82,19 +82,93 @@ build_maven_module() {
}
}
-if [[ "$MODULE" == "module5" ]]
+
+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"
- build_gradle_module "spring-boot/hazelcast/hazelcast-embedded-cache"
- build_gradle_module "spring-boot/hazelcast/hazelcast-client-server"
echo ""
echo "+++"
@@ -105,6 +179,7 @@ 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"
@@ -130,9 +205,14 @@ then
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"
@@ -182,4 +262,4 @@ then
echo "+++"
echo "+++ MODULE 4 SUCCESSFUL"
echo "+++"
-fi
\ No newline at end of file
+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/spring-boot/.DS_Store b/core-java/heapdump/.DS_Store
similarity index 75%
rename from spring-boot/.DS_Store
rename to core-java/heapdump/.DS_Store
index 076aaabe5..d99c736e1 100644
Binary files a/spring-boot/.DS_Store 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