diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..8826150 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,38 @@ +name: "CI Develop" + +on: + push: + branches: + - '**' + - '!main' + - '!master' + paths: + - '**' + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: normalize branch name for tagging + run: | + NORM_TAG=$(echo "$GITHUB_REF_NAME" | tr -s "/" "-") + echo "NORM_TAG=$NORM_TAG" >> $GITHUB_ENV + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push on GitHub packages + uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ env.NORM_TAG }} \ No newline at end of file diff --git a/.github/workflows/build_prod-uat.yaml b/.github/workflows/build_prod-uat.yaml new file mode 100644 index 0000000..e35d325 --- /dev/null +++ b/.github/workflows/build_prod-uat.yaml @@ -0,0 +1,36 @@ +name: "CI RCs - PROD" + +on: + push: + tags: + - 'v*' + - '*-rc' + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: normalize branch name for tagging + run: | + NORM_TAG=$(echo "$GITHUB_REF_NAME" | tr -s "/" "-") + echo "NORM_TAG=$NORM_TAG" >> $GITHUB_ENV + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push on GitHub packages + uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ env.NORM_TAG }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..073c893 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +## BUILD ## +FROM maven:3.8.3-openjdk-17 AS build + +WORKDIR /app + +COPY . . + +RUN mvn -q clean package -Dmaven.test.skip=true + + +## RUN ## +FROM openjdk:17-alpine + +COPY --from=build /app/target/*.jar /app/app.jar + +ENTRYPOINT ["java", "-jar", "/app/app.jar"] \ No newline at end of file diff --git a/config/application.properties b/config/application.properties new file mode 100644 index 0000000..553af30 --- /dev/null +++ b/config/application.properties @@ -0,0 +1,2 @@ +aws.profile=default +aws.region=eu-south-1 diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..5ca90ea --- /dev/null +++ b/lombok.config @@ -0,0 +1,3 @@ +lombok.addLombokGeneratedAnnotation = true + +lombok.log.custom.declaration = it.pagopa.sh.shperformancetest.abstractions.log.PnLogger.getLogger(NAME) \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8a8fb22 --- /dev/null +++ b/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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /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/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/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 https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%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/pom.xml b/pom.xml new file mode 100644 index 0000000..1835dcf --- /dev/null +++ b/pom.xml @@ -0,0 +1,267 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.2 + + + it.pagopa.interop.signalhub.persister + signalhub-signal-persister + 0.0.1-SNAPSHOT + signalhub signals persister + Signals persister microservice + + 17 + UTF-8 + 5.6.15.Final + 2.11.0 + 2.8.0 + 0.2.1 + 2.3.1 + 1.18.24 + 0.2.0 + + 1.5.1.Final + 2.13.4 + 0.00 + + + + + io.awspring.cloud + spring-cloud-aws-dependencies + 3.0.1 + pom + import + + + + + + com.amazonaws + aws-xray-recorder-sdk-apache-http + ${com.amazonaws.version} + + + + + com.amazonaws + aws-xray-recorder-sdk-spring + ${com.amazonaws.version} + + + javax.xml.bind + jaxb-api + ${javax.xml.bind.version} + + + org.openapitools + jackson-databind-nullable + ${org.openapitools.version} + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-reactor-netty + + + io.springfox + springfox-swagger2 + ${io.springfox.version} + + + io.springfox + springfox-swagger-ui + ${io.springfox.version} + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${org.projectlombok.mapstruct.version} + + + org.apache.commons + commons-lang3 + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + + io.projectreactor + reactor-test + test + + + + com.h2database + h2 + runtime + + + + io.r2dbc + r2dbc-h2 + runtime + + + + io.r2dbc + r2dbc-postgresql + runtime + + + + io.awspring.cloud + spring-cloud-aws-starter-sqs + + + + software.amazon.awssdk + sts + + + org.liquibase + liquibase-core + 4.23.0 + + + com.auth0 + java-jwt + 4.3.0 + + + org.modelmapper + modelmapper + 3.1.0 + + + + org.testcontainers + localstack + 1.17.6 + test + + + org.postgresql + postgresql + runtime + + + + org.mockito + mockito-inline + 5.2.0 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.1.2 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + + + org.projectlombok + lombok + 1.18.24 + + + org.mapstruct + mapstruct-processor + 1.5.1.Final + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + + **/config/** + **/entity/** + **/queue/model/** + **/SignalHubSignalsPersisterApplication.class + + + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/SignalHubSignalsPersisterApplication.java b/src/main/java/it/pagopa/interop/signalhub/persister/SignalHubSignalsPersisterApplication.java new file mode 100644 index 0000000..59a11c0 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/SignalHubSignalsPersisterApplication.java @@ -0,0 +1,17 @@ +package it.pagopa.interop.signalhub.persister; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.r2dbc.config.EnableR2dbcAuditing; + +@Slf4j +@SpringBootApplication +@EnableR2dbcAuditing +public class SignalHubSignalsPersisterApplication { + + public static void main(String[] args) { + SpringApplication.run(SignalHubSignalsPersisterApplication.class, args); + } + +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/config/AwsBeanBuilder.java b/src/main/java/it/pagopa/interop/signalhub/persister/config/AwsBeanBuilder.java new file mode 100644 index 0000000..cb20932 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/config/AwsBeanBuilder.java @@ -0,0 +1,71 @@ +package it.pagopa.interop.signalhub.persister.config; + +import io.awspring.cloud.sqs.config.SqsBootstrapConfiguration; +import io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory; +import io.awspring.cloud.sqs.listener.acknowledgement.handler.AcknowledgementMode; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sqs.SqsAsyncClient; +import java.net.URI; + + +@Import(SqsBootstrapConfiguration.class) +@Configuration +@Slf4j +public class AwsBeanBuilder { + private final AwsPropertiesConfig props; + + public AwsBeanBuilder(AwsPropertiesConfig props) { + this.props = props; + } + + @Bean + public SqsAsyncClient sqsAsyncClient() { + return configureBuilder(SqsAsyncClient.builder(), props.getSqsEndpoint()); + } + + @Bean + public SqsMessageListenerContainerFactory defaultSqsListenerContainerFactory() { + return SqsMessageListenerContainerFactory + .builder() + .configure(options -> options + .acknowledgementMode(AcknowledgementMode.ON_SUCCESS) + .maxConcurrentMessages(10) + .maxMessagesPerPoll(10)) + .sqsAsyncClient(sqsAsyncClient()) + .build(); + } + + private C configureBuilder(AwsClientBuilder builder, String endpoint) { + if( props != null ) { + + String profileName = props.getProfile(); + if( StringUtils.isNotBlank( profileName ) ) { + builder.credentialsProvider( ProfileCredentialsProvider.create( profileName ) ); + } else { + log.debug("Using WebIdentityTokenFileCredentialsProvider"); + builder.credentialsProvider( WebIdentityTokenFileCredentialsProvider.create() ); + } + + String regionCode = props.getRegion(); + if( StringUtils.isNotBlank( regionCode )) { + log.debug("Setting region to: {}", regionCode); + builder.region( Region.of( regionCode )); + } + + if( StringUtils.isNotBlank( endpoint )) { + builder.endpointOverride(URI.create(endpoint)); + } + + } + + return builder.build(); + } +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/config/AwsPropertiesConfig.java b/src/main/java/it/pagopa/interop/signalhub/persister/config/AwsPropertiesConfig.java new file mode 100644 index 0000000..d06d333 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/config/AwsPropertiesConfig.java @@ -0,0 +1,16 @@ +package it.pagopa.interop.signalhub.persister.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "aws") +public class AwsPropertiesConfig { + private String profile; + private String region; + private String sqsEndpoint; +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/config/ReactorConfiguration.java b/src/main/java/it/pagopa/interop/signalhub/persister/config/ReactorConfiguration.java new file mode 100644 index 0000000..81d368e --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/config/ReactorConfiguration.java @@ -0,0 +1,20 @@ +package it.pagopa.interop.signalhub.persister.config; + + +import it.pagopa.interop.signalhub.persister.logging.ContextLifter; +import it.pagopa.interop.signalhub.persister.logging.MdcContextLifter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import reactor.core.publisher.Hooks; +import reactor.core.publisher.Operators; + +@Configuration +public class ReactorConfiguration { + + + @Bean + public void contextLifterConfiguration() { + Hooks.onEachOperator(MdcContextLifter.class.getSimpleName(), + Operators.lift((sc, sub) -> new ContextLifter<>(sub))); + } +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/config/SignalHubPersisterConfig.java b/src/main/java/it/pagopa/interop/signalhub/persister/config/SignalHubPersisterConfig.java new file mode 100644 index 0000000..54b1bf9 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/config/SignalHubPersisterConfig.java @@ -0,0 +1,14 @@ +package it.pagopa.interop.signalhub.persister.config; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "pdnd.signal-hub") +public class SignalHubPersisterConfig { +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/entity/DeadSignal.java b/src/main/java/it/pagopa/interop/signalhub/persister/entity/DeadSignal.java new file mode 100644 index 0000000..ba61682 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/entity/DeadSignal.java @@ -0,0 +1,37 @@ +package it.pagopa.interop.signalhub.persister.entity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; +import java.time.Instant; + +@Getter +@Setter +@Table +@ToString +public class DeadSignal { + @Id + @Column("id") + private Long id; + @Column("correlation_id") + private String correlationId; + @Column("signal_id") + private Long signalId; + @Column("object_id") + private String objectId; + @Column("eservice_id") + private String eserviceId; + @Column("object_type") + private String objectType; + @Column("signal_type") + private String signalType; + @CreatedDate + @Column("tmst_insert") + private Instant tmstInsert; + @Column("error_reason") + private String errorReason; +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/entity/Signal.java b/src/main/java/it/pagopa/interop/signalhub/persister/entity/Signal.java new file mode 100644 index 0000000..7e7af0d --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/entity/Signal.java @@ -0,0 +1,38 @@ +package it.pagopa.interop.signalhub.persister.entity; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; +import java.time.Instant; + + +@Getter +@Setter +@Table +@ToString +@EqualsAndHashCode +public class Signal { + @Id + @Column("id") + private Long id; + @Column("correlation_id") + private String correlationId; + @Column("signal_id") + private Long signalId; + @Column("object_id") + private String objectId; + @Column("eservice_id") + private String eserviceId; + @Column("object_type") + private String objectType; + @Column("signal_type") + private String signalType; + @CreatedDate + @Column("tmst_insert") + private Instant tmstInsert; +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/exception/ExceptionTypeEnum.java b/src/main/java/it/pagopa/interop/signalhub/persister/exception/ExceptionTypeEnum.java new file mode 100644 index 0000000..bebff9a --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/exception/ExceptionTypeEnum.java @@ -0,0 +1,19 @@ +package it.pagopa.interop.signalhub.persister.exception; + +import lombok.Getter; + + +@Getter +public enum ExceptionTypeEnum{ + MAPPER_ERROR("MAPPER_ERROR", "The requested object could not be mapped."), + DUPLICATE_SIGNAL_ERROR("DUPLICATE_SIGNAL_ERROR", "The signal is already saved on database."); + + private final String title; + private final String message; + + + ExceptionTypeEnum(String title, String message) { + this.title = title; + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/exception/PDNDGenericException.java b/src/main/java/it/pagopa/interop/signalhub/persister/exception/PDNDGenericException.java new file mode 100644 index 0000000..9afe841 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/exception/PDNDGenericException.java @@ -0,0 +1,17 @@ +package it.pagopa.interop.signalhub.persister.exception; + +import lombok.Getter; + + +@Getter +public class PDNDGenericException extends RuntimeException { + private final ExceptionTypeEnum exceptionType; + private final String message; + + + public PDNDGenericException(ExceptionTypeEnum exceptionType, String message){ + super(message); + this.exceptionType = exceptionType; + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/logging/ContextLifter.java b/src/main/java/it/pagopa/interop/signalhub/persister/logging/ContextLifter.java new file mode 100644 index 0000000..cb1bb17 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/logging/ContextLifter.java @@ -0,0 +1,55 @@ +package it.pagopa.interop.signalhub.persister.logging; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +public class ContextLifter implements CoreSubscriber { + private final CoreSubscriber actualSubscriber; + private final Context context; + + public ContextLifter(CoreSubscriber actualSubscriber) { + this.actualSubscriber = actualSubscriber; + this.context = actualSubscriber.currentContext(); + } + + @Override + public Context currentContext() { + return context; + } + + @Override + public void onSubscribe(Subscription subscription) { + actualSubscriber.onSubscribe(subscription); + } + + @Override + public void onNext(T t) { + MdcContextLifter.setContextToMdc(context); + try { + actualSubscriber.onNext(t); + } finally { + MdcContextLifter.clearMdc(); + } + } + + @Override + public void onError(Throwable throwable) { + MdcContextLifter.setContextToMdc(context); + try { + actualSubscriber.onError(throwable); + } finally { + MdcContextLifter.clearMdc(); + } + } + + @Override + public void onComplete() { + MdcContextLifter.setContextToMdc(context); + try { + actualSubscriber.onComplete(); + } finally { + MdcContextLifter.clearMdc(); + } + } +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/logging/MdcContextLifter.java b/src/main/java/it/pagopa/interop/signalhub/persister/logging/MdcContextLifter.java new file mode 100644 index 0000000..8e18356 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/logging/MdcContextLifter.java @@ -0,0 +1,40 @@ +package it.pagopa.interop.signalhub.persister.logging; + +import org.slf4j.MDC; +import reactor.core.publisher.Signal; +import reactor.util.context.Context; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +import static it.pagopa.interop.signalhub.persister.utils.Const.TRACE_ID_KEY; + +public class MdcContextLifter implements Consumer> { + + @Override + public void accept(Signal signal) { + if (!signal.isOnComplete() && !signal.isOnError()) { + Optional> context = signal.getContextView().stream() + .filter(cxt -> cxt.getKey().equals(TRACE_ID_KEY)) + .findFirst(); + + context.ifPresent(ctx -> MDC.put(TRACE_ID_KEY, (String)ctx.getValue())); + } else { + MDC.clear(); + } + } + + public static void setContextToMdc(Context context) { + context.stream().forEach(entry -> { + if (entry.getKey().equals(TRACE_ID_KEY)){ + MDC.put(TRACE_ID_KEY, (String) entry.getValue()); + } + }); + } + + public static void clearMdc(){ + MDC.clear(); + } + +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/mapper/DeadSignalMapper.java b/src/main/java/it/pagopa/interop/signalhub/persister/mapper/DeadSignalMapper.java new file mode 100644 index 0000000..d9ec9be --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/mapper/DeadSignalMapper.java @@ -0,0 +1,11 @@ +package it.pagopa.interop.signalhub.persister.mapper; + +import it.pagopa.interop.signalhub.persister.entity.DeadSignal; +import it.pagopa.interop.signalhub.persister.entity.Signal; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface DeadSignalMapper { + DeadSignal signalToDeadSignal(Signal signal); + Signal deadSignalToSignal(DeadSignal deadSignal); +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/mapper/SignalMapper.java b/src/main/java/it/pagopa/interop/signalhub/persister/mapper/SignalMapper.java new file mode 100644 index 0000000..3af4cb9 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/mapper/SignalMapper.java @@ -0,0 +1,17 @@ +package it.pagopa.interop.signalhub.persister.mapper; + +import it.pagopa.interop.signalhub.persister.entity.Signal; +import it.pagopa.interop.signalhub.persister.queue.model.SignalEvent; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + + +@Mapper(componentModel = "spring") +public interface SignalMapper { + String CORRELATION_ID_HEADER_KEY = "correlationId"; + + SignalEvent signalToSignalEvent(Signal signal); + + @Mapping(target = "correlationId", source = "correlationId") + Signal signalEventToSignal(SignalEvent signalEvent, String correlationId); +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/queue/consumer/SqsInternalListener.java b/src/main/java/it/pagopa/interop/signalhub/persister/queue/consumer/SqsInternalListener.java new file mode 100644 index 0000000..2798173 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/queue/consumer/SqsInternalListener.java @@ -0,0 +1,46 @@ +package it.pagopa.interop.signalhub.persister.queue.consumer; + + +import io.awspring.cloud.sqs.annotation.SqsListener; +import it.pagopa.interop.signalhub.persister.mapper.SignalMapper; +import it.pagopa.interop.signalhub.persister.queue.model.SignalEvent; +import it.pagopa.interop.signalhub.persister.service.SignalService; +import it.pagopa.interop.signalhub.persister.utils.Utility; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static it.pagopa.interop.signalhub.persister.utils.Const.TRACE_ID_KEY; + + +@Slf4j +@Component +@AllArgsConstructor +public class SqsInternalListener { + private SignalService signalService; + private SignalMapper signalMapper; + + + @SqsListener(value = "${aws.internal-queue-name}") + public CompletableFuture pullFromAwsInternalQueue(@Payload String node, @Headers Map headers) { + String correlationId = (String) headers.get(SignalMapper.CORRELATION_ID_HEADER_KEY); + + return Mono.just(node) + .contextWrite(Context.of(TRACE_ID_KEY, correlationId)) + .doOnNext(json -> log.info("payloadBody: {}, headers: {}, PullFromInternalQueue received input", node, headers)) + .map(json -> Utility.jsonToObject(node, SignalEvent.class)) + .map(signalEvent -> signalMapper.signalEventToSignal(signalEvent, correlationId)) + .flatMap(signalEvent -> signalService.signalServiceFlow(signalEvent) + .contextWrite(Context.of(TRACE_ID_KEY, correlationId)) + ) + .then() + .toFuture(); + } +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/queue/model/SignalEvent.java b/src/main/java/it/pagopa/interop/signalhub/persister/queue/model/SignalEvent.java new file mode 100644 index 0000000..0e384ae --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/queue/model/SignalEvent.java @@ -0,0 +1,23 @@ +package it.pagopa.interop.signalhub.persister.queue.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + + +@Getter +@Setter +@ToString +@EqualsAndHashCode +public class SignalEvent { + private SignalType signalType; + + private String objectId; + + private String objectType; + + private String eserviceId; + + private Long signalId; +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/queue/model/SignalType.java b/src/main/java/it/pagopa/interop/signalhub/persister/queue/model/SignalType.java new file mode 100644 index 0000000..8e13201 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/queue/model/SignalType.java @@ -0,0 +1,36 @@ +package it.pagopa.interop.signalhub.persister.queue.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum SignalType { + + CREATE("CREATE"), + + UPDATE("UPDATE"), + + DELETE("DELETE"), + + SEEDUPDATE("SEEDUPDATE"); + + private String code; + + SignalType(String code) { + this.code = code; + } + + @JsonValue() + private String getCode() { + return this.code; + } + + @JsonCreator() + private SignalType fromCode(String code) { + for(SignalType signalType: SignalType.values()) { + if(signalType.code.equals(code)) { + return signalType; + } + } + throw new IllegalArgumentException("Unexpected error on enum creation"); + } +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/repository/DeadSignalRepository.java b/src/main/java/it/pagopa/interop/signalhub/persister/repository/DeadSignalRepository.java new file mode 100644 index 0000000..6fd4ec8 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/repository/DeadSignalRepository.java @@ -0,0 +1,9 @@ +package it.pagopa.interop.signalhub.persister.repository; + +import it.pagopa.interop.signalhub.persister.entity.DeadSignal; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface DeadSignalRepository extends ReactiveCrudRepository { +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/repository/SignalRepository.java b/src/main/java/it/pagopa/interop/signalhub/persister/repository/SignalRepository.java new file mode 100644 index 0000000..c5743fe --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/repository/SignalRepository.java @@ -0,0 +1,14 @@ +package it.pagopa.interop.signalhub.persister.repository; + +import it.pagopa.interop.signalhub.persister.entity.Signal; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public interface SignalRepository extends ReactiveCrudRepository { + + @Query("SELECT * FROM SIGNAL WHERE signal_id = :signalId AND eservice_id = :eserviceId") + Mono findByIndexSignalAndEserviceId(Long signalId, String eserviceId); +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/service/SignalService.java b/src/main/java/it/pagopa/interop/signalhub/persister/service/SignalService.java new file mode 100644 index 0000000..9e642e7 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/service/SignalService.java @@ -0,0 +1,8 @@ +package it.pagopa.interop.signalhub.persister.service; + +import it.pagopa.interop.signalhub.persister.entity.Signal; +import reactor.core.publisher.Mono; + +public interface SignalService { + Mono signalServiceFlow(Signal signal); +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/service/impl/SignalServiceImpl.java b/src/main/java/it/pagopa/interop/signalhub/persister/service/impl/SignalServiceImpl.java new file mode 100644 index 0000000..e49cf9c --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/service/impl/SignalServiceImpl.java @@ -0,0 +1,74 @@ +package it.pagopa.interop.signalhub.persister.service.impl; + +import it.pagopa.interop.signalhub.persister.entity.DeadSignal; +import it.pagopa.interop.signalhub.persister.entity.Signal; +import it.pagopa.interop.signalhub.persister.mapper.DeadSignalMapper; +import it.pagopa.interop.signalhub.persister.repository.DeadSignalRepository; +import it.pagopa.interop.signalhub.persister.repository.SignalRepository; +import it.pagopa.interop.signalhub.persister.service.SignalService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Mono; +import static it.pagopa.interop.signalhub.persister.exception.ExceptionTypeEnum.DUPLICATE_SIGNAL_ERROR; + + +@Slf4j +@Service +@AllArgsConstructor +public class SignalServiceImpl implements SignalService { + private SignalRepository signalRepository; + private DeadSignalRepository deadSignalRepository; + private DeadSignalMapper deadSignalMapper; + + + @Transactional + public Mono signalServiceFlow(Signal signal) { + + return getSignalById(signal.getSignalId(), signal.getEserviceId()) + .switchIfEmpty(Mono.just(signal)) + .filter(entity -> entity.getId() == null) + .flatMap(this::createSignal) + .switchIfEmpty(Mono.defer(() -> saveToDeadSignal(signal))); + } + + private Mono getSignalById(Long signalId, String eserviceId) { + return this.signalRepository.findByIndexSignalAndEserviceId(signalId, eserviceId) + .switchIfEmpty(Mono.defer(() -> { + log.info("[{}] Signal not present with {} signalId", eserviceId, signalId); + return Mono.empty(); + })) + .doOnNext(signal -> log.info("[{}] Signal is present with {} signalId", eserviceId, signalId)); + } + + private Mono createSignal(Signal signal) { + return this.signalRepository.save(signal) + .doOnNext(data -> log.info("[{}] Signal saved with {} signal id", signal.getEserviceId(), signal.getSignalId())) + .doOnError(error -> + log.error("[{}] Error saving signal with {} signal id", signal.getEserviceId(), signal.getSignalId()) + ); + } + + private Mono createDeadSignal(DeadSignal signal) { + log.info("[{}] Save dead signal with {} signal id", signal.getEserviceId(), signal.getSignalId()); + return this.deadSignalRepository.save(signal) + .doOnNext(data -> log.info("[{}] Dead signal saved with {} signal id", signal.getEserviceId(), signal.getSignalId())) + .doOnError(error -> + log.error("[{}] Error saving dead signal with {} signal id", signal.getEserviceId(), signal.getSignalId()) + ); + + } + + private Mono getDeadSignal(Signal signal) { + DeadSignal deadSignal = deadSignalMapper.signalToDeadSignal(signal); + deadSignal.setErrorReason(DUPLICATE_SIGNAL_ERROR.toString()); + return Mono.just(deadSignal); + } + + private Mono saveToDeadSignal(Signal signal) { + return getDeadSignal(signal) + .flatMap(this::createDeadSignal) + .then(Mono.just(signal)); + } +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/utils/Const.java b/src/main/java/it/pagopa/interop/signalhub/persister/utils/Const.java new file mode 100644 index 0000000..c117dc5 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/utils/Const.java @@ -0,0 +1,5 @@ +package it.pagopa.interop.signalhub.persister.utils; + +public class Const { + public static final String TRACE_ID_KEY = "traceId"; +} diff --git a/src/main/java/it/pagopa/interop/signalhub/persister/utils/Utility.java b/src/main/java/it/pagopa/interop/signalhub/persister/utils/Utility.java new file mode 100644 index 0000000..f33af04 --- /dev/null +++ b/src/main/java/it/pagopa/interop/signalhub/persister/utils/Utility.java @@ -0,0 +1,22 @@ +package it.pagopa.interop.signalhub.persister.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.interop.signalhub.persister.exception.PDNDGenericException; +import lombok.extern.slf4j.Slf4j; + +import static it.pagopa.interop.signalhub.persister.exception.ExceptionTypeEnum.MAPPER_ERROR; + + +@Slf4j +public class Utility { + public static T jsonToObject(String json, Class tClass){ + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(json, tClass); + } catch (JsonProcessingException exception) { + log.error("exception = {}, errorReason = {}, Error during mapping an object", exception, exception.getMessage()); + throw new PDNDGenericException(MAPPER_ERROR, MAPPER_ERROR.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..3cbb16d --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,33 @@ +logging.level.root=INFO +spring.main.web-appplication-type=reactive +management.endpoint.health.show-details=always +logging.pattern.level=%2p [%X{traceId:-}] + +# Disable auto cloud formation +cloud.aws.stack.auto=false +aws.region=${AWS_REGION:us-east-1} +aws.sqs-endpoint=${AWS_SQS_ENDPOINT:http://localhost:4566/000000000000/} +aws.internal-queue-name=local-internal-signal-hub + +database.name=${DB_NAME:signal-hub} +database.host=${DATABASE_WRITER_HOST:localhost} +database.port=${DB_PORT:5432} +database.username=${DB_USER:postgres} +database.password=${DB_PASSWORD:postgres} + +#Liquibase +spring.liquibase.url=jdbc:postgresql://${database.host}:${database.port}/${database.name} +spring.liquibase.user=${database.username} +spring.liquibase.password=${database.password} +spring.liquibase.change-log=classpath:/db/changelog/changelog-master.xml +spring.liquibase.enabled=true +logging.level.liquibase=INFO + +#R2DBC - Postgres +spring.r2dbc.url=r2dbc:postgresql://${database.host}:${database.port}/${database.name} +spring.r2dbc.username=${database.username} +spring.r2dbc.password=${database.password} +spring.r2dbc.pool.max-size=20 +spring.r2dbc.pool.initial-size=5 +spring.r2dbc.pool.enabled=true +spring.data.r2dbc.repositories.enabled=true \ No newline at end of file diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml new file mode 100644 index 0000000..3c8cf85 --- /dev/null +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/init_table/changelog.xml b/src/main/resources/db/changelog/changes/init_table/changelog.xml new file mode 100644 index 0000000..6dadf9e --- /dev/null +++ b/src/main/resources/db/changelog/changes/init_table/changelog.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/init_table/init_table.sql b/src/main/resources/db/changelog/changes/init_table/init_table.sql new file mode 100644 index 0000000..b033211 --- /dev/null +++ b/src/main/resources/db/changelog/changes/init_table/init_table.sql @@ -0,0 +1,80 @@ +CREATE TABLE IF NOT EXISTS ESERVICE ( + eservice_id VARCHAR (255) NOT NULL, + producer_id VARCHAR (255) NOT NULL, + descriptor_id VARCHAR (255) NOT NULL, + event_id BIGINT, + state VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + tmst_last_edit TIMESTAMP, + UNIQUE (eservice_id, producer_id, descriptor_id), + PRIMARY KEY (eservice_id, producer_id, descriptor_id) +); +CREATE INDEX IF NOT EXISTS ESERVICE_INDEX_ID ON ESERVICE USING hash (eservice_id); +CREATE INDEX IF NOT EXISTS ESERVICE_INDEX_PRODUCER_ID ON ESERVICE USING hash (producer_id); + + +CREATE TABLE IF NOT EXISTS CONSUMER_ESERVICE ( + agreement_id VARCHAR (255) NOT NULL, + eservice_id VARCHAR (255) NOT NULL, + consumer_id VARCHAR (255) NOT NULL, + descriptor_id VARCHAR (255) NOT NULL, + event_id BIGINT, + state VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + tmst_last_edit TIMESTAMP, + UNIQUE (eservice_id, consumer_id, descriptor_id), + PRIMARY KEY (eservice_id, consumer_id, descriptor_id) +); +CREATE INDEX IF NOT EXISTS CONSUMER_ESERVICE_INDEX_ID ON CONSUMER_ESERVICE USING hash (eservice_id); +CREATE INDEX IF NOT EXISTS CONSUMER_ESERVICE_INDEX_CONSUMER_ID ON CONSUMER_ESERVICE USING hash (consumer_id); +CREATE INDEX IF NOT EXISTS CONSUMER_ESERVICE_INDEX_DESCRIPTOR_ID ON CONSUMER_ESERVICE USING hash (descriptor_id); + + +CREATE TABLE IF NOT EXISTS SIGNAL ( + id SERIAL PRIMARY KEY, + correlation_id VARCHAR(255) NOT NULL, + signal_id BIGINT NOT NULL, + object_id VARCHAR (255) NOT NULL, + eservice_id VARCHAR (255) NOT NULL, + object_type VARCHAR (255) NOT NULL, + signal_type VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE (signal_id, eservice_id) +); +CREATE INDEX IF NOT EXISTS SIGNAL_INDEX_SIGNAL_ID ON SIGNAL USING hash (signal_id); +CREATE INDEX IF NOT EXISTS SIGNAL_INDEX_ESERVICE_ID ON SIGNAL USING hash (eservice_id); + + +CREATE TABLE IF NOT EXISTS DEAD_SIGNAL ( + id SERIAL PRIMARY KEY, + correlation_id VARCHAR(255) NOT NULL, + signal_id BIGINT NOT NULL, + object_id VARCHAR (255) NOT NULL, + eservice_id VARCHAR (255) NOT NULL, + object_type VARCHAR (255) NOT NULL, + signal_type VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + error_reason VARCHAR(255) NOT NULL +); + + +CREATE TABLE IF NOT EXISTS TRACING_BATCH ( + batch_id SERIAL PRIMARY KEY, + state VARCHAR (255) NOT NULL, + type VARCHAR (50) NOT NULL, + last_event_id BIGINT, + tmst_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + +CREATE TABLE IF NOT EXISTS DEAD_EVENT ( + event_tmp_id SERIAL PRIMARY KEY, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + error_reason VARCHAR(255) NOT NULL, + event_id BIGINT NOT NULL, + event_type VARCHAR (255) NOT NULL, + object_type VARCHAR (255) NOT NULL, + descriptor_id VARCHAR (255), + eservice_id VARCHAR (255), + agreement_id VARCHAR (255) +); \ No newline at end of file diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/LocalStackTestConfig.java b/src/test/java/it/pagopa/interop/signalhub/persister/LocalStackTestConfig.java new file mode 100644 index 0000000..aa9b22b --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/LocalStackTestConfig.java @@ -0,0 +1,50 @@ +package it.pagopa.interop.signalhub.persister; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.core.io.ClassPathResource; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.time.Duration; + +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.DYNAMODB; +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS; + +/** + * Classe che permette di creare un container Docker di LocalStack. + * Il container (e quindi la classe) può essere condivisa tra più classi di test. + * Per utilizzare questa classe, le classi di test dovranno essere annotate con + * @Import(LocalStackTestConfig.class) + */ +@TestConfiguration +public class LocalStackTestConfig { + + static LocalStackContainer localStack = + new LocalStackContainer(DockerImageName.parse("localstack/localstack:1.0.4").asCompatibleSubstituteFor("localstack/localstack")) + .withServices(SQS) + .withClasspathResourceMapping("testcontainers/init.sh", + "/docker-entrypoint-initaws.d/make-storages.sh", BindMode.READ_ONLY) + .withClasspathResourceMapping("testcontainers/credentials", + "/root/.aws/credentials", BindMode.READ_ONLY) + .withNetworkAliases("localstack") + .withNetwork(Network.builder().build()) + .waitingFor(Wait.forLogMessage(".*Initialization terminated.*", 1) + .withStartupTimeout(Duration.ofSeconds(180))); + + static { + localStack.start(); + System.setProperty("aws.sqs-endpoint", localStack.getEndpointOverride(SQS).toString()); + try { + System.setProperty("aws.sharedCredentialsFile", new ClassPathResource("testcontainers/credentials").getFile().getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + +} diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/exception/PDNDGenericExceptionTest.java b/src/test/java/it/pagopa/interop/signalhub/persister/exception/PDNDGenericExceptionTest.java new file mode 100644 index 0000000..a7c411b --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/exception/PDNDGenericExceptionTest.java @@ -0,0 +1,14 @@ +package it.pagopa.interop.signalhub.persister.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class PDNDGenericExceptionTest { + + @Test + void exceptionTest() { + assertDoesNotThrow(() -> new PDNDGenericException(ExceptionTypeEnum.MAPPER_ERROR, ExceptionTypeEnum.MAPPER_ERROR.getMessage())); + assertDoesNotThrow(() -> new PDNDGenericException(ExceptionTypeEnum.DUPLICATE_SIGNAL_ERROR, ExceptionTypeEnum.DUPLICATE_SIGNAL_ERROR.getMessage())); + } +} diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/mapper/DeadSignalMapperTest.java b/src/test/java/it/pagopa/interop/signalhub/persister/mapper/DeadSignalMapperTest.java new file mode 100644 index 0000000..df39717 --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/mapper/DeadSignalMapperTest.java @@ -0,0 +1,119 @@ +package it.pagopa.interop.signalhub.persister.mapper; + +import it.pagopa.interop.signalhub.persister.entity.DeadSignal; +import it.pagopa.interop.signalhub.persister.entity.Signal; +import it.pagopa.interop.signalhub.persister.queue.model.SignalType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import static org.junit.jupiter.api.Assertions.*; + + +class DeadSignalMapperTest { + private DeadSignalMapper deadSignalMapper; + private Long signalId; + private String objectId; + private String correlationId; + private String eserviceId; + private String objectType; + private Long indexSignal; + private String errorReason; + private String signalType; + + + @BeforeEach + void preTest() { + this.setUp(); + } + + @Test + void signalToDeadSignalTest() { + Signal signal = getSignal(); + DeadSignal deadSignal = deadSignalMapper.signalToDeadSignal(signal); + assertNotNull(deadSignal); + assertEquals(deadSignal.getSignalId(), this.indexSignal); + assertEquals(deadSignal.getObjectId(), this.objectId); + assertEquals(deadSignal.getEserviceId(), this.eserviceId); + assertEquals(deadSignal.getCorrelationId(), this.correlationId); + assertEquals(deadSignal.getSignalType(), this.signalType); + assertEquals(deadSignal.getObjectType(), this.objectType); + } + + @Test + void signalToDeadSignalNullCaseTest() { + DeadSignal deadSignal = deadSignalMapper.signalToDeadSignal(null); + assertNull(deadSignal); + } + + @Test + void signalToDeadSignalWithSignalTypeNullTest() { + Signal signal = getSignal(); + signal.setSignalType(null); + DeadSignal deadSignal = deadSignalMapper.signalToDeadSignal(signal); + assertNotNull(deadSignal); + assertNull(deadSignal.getSignalType()); + } + + @Test + void deadSignalToSignalTest() { + DeadSignal deadSignal = getDeadSignal(); + Signal signal = deadSignalMapper.deadSignalToSignal(deadSignal); + assertNotNull(signal); + assertEquals(signal.getSignalId(), this.indexSignal); + assertEquals(signal.getObjectId(), this.objectId); + assertEquals(signal.getEserviceId(), this.eserviceId); + assertEquals(signal.getCorrelationId(), this.correlationId); + assertEquals(signal.getSignalType(), this.signalType); + assertEquals(signal.getObjectType(), this.objectType); + } + + @Test + void deadSignalToSignalNullCaseTest() { + Signal signal = deadSignalMapper.deadSignalToSignal(null); + assertNull(signal); + } + + @Test + void deadSignalToSignalWithSignalTypeNullTest() { + DeadSignal deadSignal = getDeadSignal(); + deadSignal.setSignalType(null); + Signal signal = deadSignalMapper.deadSignalToSignal(deadSignal); + assertNotNull(signal); + assertNull(signal.getSignalType()); + } + + private Signal getSignal() { + Signal signal = new Signal(); + signal.setSignalId(this.signalId); + signal.setSignalType(this.signalType); + signal.setObjectType(this.objectType); + signal.setCorrelationId(this.correlationId); + signal.setEserviceId(this.eserviceId); + signal.setObjectId(this.objectId); + return signal; + } + + private DeadSignal getDeadSignal() { + DeadSignal deadSignal = new DeadSignal(); + deadSignal.setSignalType(this.signalType); + deadSignal.setObjectType(this.objectType); + deadSignal.setEserviceId(this.eserviceId); + deadSignal.setObjectId(this.objectId); + deadSignal.setCorrelationId(this.correlationId); + deadSignal.setSignalId(this.signalId); + deadSignal.setErrorReason(this.errorReason); + return deadSignal; + } + + private void setUp() { + this.signalId = 0L; + this.objectId = "OBJ1"; + this.correlationId = "0A"; + this.objectType = "ESERVICE"; + this.eserviceId = "OBJ1"; + this.indexSignal = 0L; + this.errorReason = "404 Not Found"; + this.signalType = SignalType.CREATE.toString(); + this.deadSignalMapper = Mappers.getMapper(DeadSignalMapper.class); + } +} \ No newline at end of file diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/mapper/SignalMapperTest.java b/src/test/java/it/pagopa/interop/signalhub/persister/mapper/SignalMapperTest.java new file mode 100644 index 0000000..b3ecad0 --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/mapper/SignalMapperTest.java @@ -0,0 +1,123 @@ +package it.pagopa.interop.signalhub.persister.mapper; + +import it.pagopa.interop.signalhub.persister.entity.Signal; +import it.pagopa.interop.signalhub.persister.queue.model.SignalEvent; +import it.pagopa.interop.signalhub.persister.queue.model.SignalType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import static org.junit.jupiter.api.Assertions.*; + + +class SignalMapperTest { + private SignalMapper signalMapper; + private Long signalId; + private String objectId; + private String correlationId; + private String eserviceId; + private String objectType; + private String signalType; + + + @BeforeEach + void preTest() { + this.setUp(); + } + + @Test + void signalToSignalEventTest() { + Signal signal = getSignal(); + SignalEvent signalEvent = signalMapper.signalToSignalEvent(signal); + assertNotNull(signalEvent); + assertEquals(signalEvent.getObjectId(), this.objectId); + assertEquals(signalEvent.getEserviceId(), this.eserviceId); + assertEquals(SignalType.CREATE, signalEvent.getSignalType()); + assertEquals(signalEvent.getObjectType(), this.objectType); + assertEquals(signalEvent.getSignalId(), this.signalId); + } + + @Test + void signalToSignalEventNullCaseTest() { + SignalEvent signalEvent = signalMapper.signalToSignalEvent(null); + assertNull(signalEvent); + } + + @Test + void signalToSignalEventWithSignalTypeNullTest() { + Signal signal = getSignal(); + signal.setSignalType(null); + SignalEvent signalEvent = signalMapper.signalToSignalEvent(signal); + assertNotNull(signalEvent); + assertNull(signalEvent.getSignalType()); + } + + @Test + void signalEventToSignalTest() { + SignalEvent signalEvent = getSignalEvent(); + Signal signal = signalMapper.signalEventToSignal(signalEvent, this.correlationId); + assertNotNull(signal); + assertEquals(signal.getSignalId(), this.signalId); + assertEquals(signal.getObjectId(), this.objectId); + assertEquals(signal.getEserviceId(), this.eserviceId); + assertEquals(signal.getCorrelationId(), this.correlationId); + assertEquals(signal.getSignalType(), this.signalType); + assertEquals(signal.getObjectType(), this.objectType); + } + + @Test + void signalEventToSignalNullCaseTest() { + Signal signal = signalMapper.signalEventToSignal(null, null); + assertNull(signal); + + signal = signalMapper.signalEventToSignal(null, this.correlationId); + assertNotNull(signal); + + SignalEvent signalEvent = getSignalEvent(); + signal = signalMapper.signalEventToSignal(signalEvent, null); + assertNotNull(signal); + assertEquals(signal.getSignalId(), this.signalId); + assertEquals(signal.getObjectId(), this.objectId); + assertEquals(signal.getEserviceId(), this.eserviceId); + assertEquals(signal.getObjectType(), this.objectType); + } + + @Test + void signalEventToSignalWithSignalTypeNullTest() { + SignalEvent signalEvent = getSignalEvent(); + signalEvent.setSignalType(null); + Signal signal = signalMapper.signalEventToSignal(signalEvent, this.correlationId); + assertNotNull(signal); + assertNull(signal.getSignalType()); + } + + private Signal getSignal() { + Signal signal = new Signal(); + signal.setSignalId(this.signalId); + signal.setSignalType(this.signalType); + signal.setObjectType(this.objectType); + signal.setCorrelationId(this.correlationId); + signal.setEserviceId(this.eserviceId); + signal.setObjectId(this.objectId); + return signal; + } + + private SignalEvent getSignalEvent() { + SignalEvent signalEvent = new SignalEvent(); + signalEvent.setSignalType(SignalType.CREATE); + signalEvent.setObjectType(this.objectType); + signalEvent.setEserviceId(this.eserviceId); + signalEvent.setObjectId(this.objectId); + signalEvent.setSignalId(this.signalId); + return signalEvent; + } + + private void setUp() { + this.signalId = 0L; + this.objectId = "OBJ1"; + this.correlationId = "0A"; + this.objectType = "ESERVICE"; + this.eserviceId = "OBJ1"; + this.signalType = SignalType.CREATE.toString(); + this.signalMapper = Mappers.getMapper(SignalMapper.class); + } +} \ No newline at end of file diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/queue/consumer/SqsInternalListenerTest.java b/src/test/java/it/pagopa/interop/signalhub/persister/queue/consumer/SqsInternalListenerTest.java new file mode 100644 index 0000000..a692ec0 --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/queue/consumer/SqsInternalListenerTest.java @@ -0,0 +1,174 @@ +package it.pagopa.interop.signalhub.persister.queue.consumer; + +import it.pagopa.interop.signalhub.persister.entity.Signal; +import it.pagopa.interop.signalhub.persister.exception.PDNDGenericException; +import it.pagopa.interop.signalhub.persister.mapper.SignalMapper; +import it.pagopa.interop.signalhub.persister.queue.model.SignalEvent; +import it.pagopa.interop.signalhub.persister.queue.model.SignalType; +import it.pagopa.interop.signalhub.persister.service.SignalService; +import it.pagopa.interop.signalhub.persister.utils.Utility; +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 org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import static it.pagopa.interop.signalhub.persister.exception.ExceptionTypeEnum.MAPPER_ERROR; +import static org.junit.jupiter.api.Assertions.*; + + +@ExtendWith(MockitoExtension.class) +class SqsInternalListenerTest { + @InjectMocks + private SqsInternalListener sqsInternalListener; + @Mock + private SignalService signalService; + @Mock + private SignalMapper signalMapper; + private MockedStatic utility; + private String objectId; + private String correlationId; + private String eserviceId; + private String objectType; + private Long signalId; + + + @BeforeEach + void preTest() { + utility = Mockito.mockStatic(Utility.class); + this.setUp(); + } + + @AfterEach + void postTest() { + if(utility != null) { + utility.close(); + } + } + + @Test + void pullFromAwsInternalQueueTest() throws ExecutionException, InterruptedException { + String jsoNode = """ + { + "signalType": "CREATE", + "objectId": "OBJ1", + "objectType": "T1", + "eserviceId": "E1", + "indexSignal": "000001" + } + """; + SignalEvent signalEvent = getSignalEvent(); + + utility.when(() -> Utility.jsonToObject(jsoNode, SignalEvent.class)) + .thenReturn(signalEvent); + + Map headers = new HashMap<>(); + headers.put(SignalMapper.CORRELATION_ID_HEADER_KEY, correlationId); + + SignalMapper sm = Mappers.getMapper(SignalMapper.class); + Signal signal = sm.signalEventToSignal(signalEvent, correlationId); + + Mockito + .when(signalMapper.signalEventToSignal(signalEvent, correlationId)) + .thenReturn(signal); + + Mockito + .when(signalService.signalServiceFlow(signal)) + .thenReturn(Mono.just(signal)); + + sqsInternalListener.pullFromAwsInternalQueue(jsoNode, headers).get(); + + + Mockito + .verify(signalMapper, Mockito.timeout(1000).times(1)) + .signalEventToSignal(signalEvent, correlationId); + + + ArgumentCaptor signalEventArgumentCaptor = ArgumentCaptor.forClass(SignalEvent.class); + ArgumentCaptor captorString = ArgumentCaptor.forClass(String.class); + + Mockito + .verify(signalMapper, Mockito.timeout(1000).times(1)) + .signalEventToSignal(signalEventArgumentCaptor.capture(), captorString.capture()); + + assertNotNull(signalEventArgumentCaptor.getValue()); + assertNotNull(captorString.getValue()); + + assertEquals(signalEvent, + Objects.requireNonNull(signalEventArgumentCaptor.getValue())); + assertEquals(correlationId, + Objects.requireNonNull(captorString.getValue())); + + + Mockito + .verify(signalService, Mockito.timeout(1000).times(1)) + .signalServiceFlow(signal); + } + + @Test + void pullFromAwsInternalQueueJsonNullTest() { + String jsoNode = null; + Map headers = new HashMap<>(); + headers.put(SignalMapper.CORRELATION_ID_HEADER_KEY, correlationId); + Assertions.assertThrows(NullPointerException.class, () -> + sqsInternalListener.pullFromAwsInternalQueue(jsoNode, headers) + .get()); + } + + @Test + void pullFromAwsInternalQueueExceptionToObjectTest() { + String jsoNode = """ + { + "signalType": "CREATE", + "objectId": "OBJ1", + "objectType": "T1", + "eserviceId": "E1", + "indexSignal": "000001" + } + """; + SignalEvent signalEvent = getSignalEvent(); + + utility.when(() -> Utility.jsonToObject(jsoNode, SignalEvent.class)) + .thenThrow(new PDNDGenericException(MAPPER_ERROR, MAPPER_ERROR.getMessage())); + + Map headers = new HashMap<>(); + headers.put(SignalMapper.CORRELATION_ID_HEADER_KEY, correlationId); + + ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> + sqsInternalListener.pullFromAwsInternalQueue(jsoNode, headers) + .get()); + + Mockito.verify(signalMapper, Mockito.timeout(1000).times(0)) + .signalEventToSignal(signalEvent, correlationId); + + Mockito.verify(signalService, Mockito.timeout(1000).times(0)) + .signalServiceFlow(Mockito.any()); + + Assertions.assertEquals(MAPPER_ERROR.getMessage(), exception.getCause().getMessage()); + } + + private SignalEvent getSignalEvent() { + SignalEvent signalEvent = new SignalEvent(); + signalEvent.setSignalType(SignalType.CREATE); + signalEvent.setObjectType(this.objectType); + signalEvent.setEserviceId(this.eserviceId); + signalEvent.setObjectId(this.objectId); + signalEvent.setSignalId(this.signalId); + return signalEvent; + } + + private void setUp() { + this.objectId = "OBJ1"; + this.correlationId = "0A"; + this.eserviceId = "OBJ1"; + this.objectType = "ESERVICE"; + this.signalId = 0L; + } +} \ No newline at end of file diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/service/SignalServiceImplTest.java b/src/test/java/it/pagopa/interop/signalhub/persister/service/SignalServiceImplTest.java new file mode 100644 index 0000000..7778afc --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/service/SignalServiceImplTest.java @@ -0,0 +1,201 @@ +package it.pagopa.interop.signalhub.persister.service; + +import it.pagopa.interop.signalhub.persister.entity.DeadSignal; +import it.pagopa.interop.signalhub.persister.entity.Signal; +import it.pagopa.interop.signalhub.persister.mapper.DeadSignalMapper; +import it.pagopa.interop.signalhub.persister.queue.model.SignalType; +import it.pagopa.interop.signalhub.persister.repository.DeadSignalRepository; +import it.pagopa.interop.signalhub.persister.repository.SignalRepository; +import it.pagopa.interop.signalhub.persister.service.impl.SignalServiceImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; +import java.util.Objects; +import static org.junit.jupiter.api.Assertions.*; + + +@ExtendWith(MockitoExtension.class) +class SignalServiceImplTest { + @InjectMocks + SignalServiceImpl signalServiceImpl; + @Mock + private SignalRepository signalRepository; + @Mock + private DeadSignalRepository deadSignalRepository; + @Mock + private DeadSignalMapper deadSignalMapper; + + private Long id; + private Long signalId; + private String objectId; + private String correlationId; + private String eserviceId; + private String objectType; + private String signalType; + + + @BeforeEach + void preTest(){ + this.setUp(); + } + + @Test + void signalServiceFlowTest() { + Signal signalToSave = getSignal(); + signalToSave.setId(null); + + Mockito + .when(signalRepository.findByIndexSignalAndEserviceId(signalToSave.getSignalId(), signalToSave.getEserviceId())) + .thenReturn(Mono.empty()); + + Mockito + .when(signalRepository.save(signalToSave)) + .thenReturn(Mono.just(signalToSave)); + + + signalServiceImpl.signalServiceFlow(signalToSave) + .flatMap(sig -> { + Assertions.assertNotNull(sig); + Assertions.assertEquals(signalToSave, sig); + return Mono.empty(); + } + ).block(); + + + ArgumentCaptor captorString = ArgumentCaptor.forClass(String.class); + ArgumentCaptor captorLong = ArgumentCaptor.forClass(Long.class); + Mockito.verify(signalRepository, Mockito.timeout(1000).times(1)) + .findByIndexSignalAndEserviceId(captorLong.capture(), captorString.capture()); + + assertNotNull(captorLong.getValue()); + assertNotNull(captorString.getValue()); + + assertEquals(signalToSave.getSignalId(), + Objects.requireNonNull(captorLong.getValue())); + + assertEquals(signalToSave.getEserviceId(), + Objects.requireNonNull(captorString.getValue())); + + + ArgumentCaptor signalArgumentCaptor = ArgumentCaptor.forClass(Signal.class); + Mockito.verify(signalRepository, Mockito.timeout(1000).times(1)) + .save(signalArgumentCaptor.capture()); + + assertNotNull(signalArgumentCaptor.getValue()); + assertEquals(signalToSave, + Objects.requireNonNull(signalArgumentCaptor.getValue())); + } + + + @Test + void signalServiceFlowDuplicateSignalTest() { + Signal signalToUpdate = getSignal(); + Mockito + .when(signalRepository.findByIndexSignalAndEserviceId(signalToUpdate.getSignalId(), signalToUpdate.getEserviceId())) + .thenReturn(Mono.just(signalToUpdate)); + + DeadSignalMapper dsm = Mappers.getMapper(DeadSignalMapper.class); + DeadSignal deadSignal = dsm.signalToDeadSignal(signalToUpdate); + Mockito + .when(deadSignalMapper.signalToDeadSignal(signalToUpdate)) + .thenReturn(deadSignal); + + Mockito + .when(deadSignalRepository.save(deadSignal)) + .thenReturn(Mono.empty()); + + signalServiceImpl.signalServiceFlow(signalToUpdate) + .flatMap(sig -> { + Assertions.assertNotNull(sig); + Assertions.assertEquals(signalToUpdate, sig); + return Mono.empty(); + } + ).block(); + + + ArgumentCaptor captorString = ArgumentCaptor.forClass(String.class); + ArgumentCaptor captorLong = ArgumentCaptor.forClass(Long.class); + Mockito.verify(signalRepository, Mockito.timeout(1000).times(1)) + .findByIndexSignalAndEserviceId(captorLong.capture(), captorString.capture()); + + assertNotNull(captorLong.getValue()); + assertNotNull(captorString.getValue()); + + assertEquals(signalToUpdate.getSignalId(), + Objects.requireNonNull(captorLong.getValue())); + + assertEquals(signalToUpdate.getEserviceId(), + Objects.requireNonNull(captorString.getValue())); + + + ArgumentCaptor signalArgumentCaptor = ArgumentCaptor.forClass(Signal.class); + Mockito.verify(deadSignalMapper, Mockito.timeout(1000).times(1)) + .signalToDeadSignal(signalArgumentCaptor.capture()); + + assertNotNull(signalArgumentCaptor.getValue()); + assertEquals(signalToUpdate, + Objects.requireNonNull(signalArgumentCaptor.getValue())); + + + ArgumentCaptor deadSignalArgumentCaptor = ArgumentCaptor.forClass(DeadSignal.class); + Mockito.verify(deadSignalRepository, Mockito.timeout(1000).times(1)) + .save(deadSignalArgumentCaptor.capture()); + + assertNotNull(deadSignalArgumentCaptor.getValue()); + assertEquals(deadSignal, + Objects.requireNonNull(deadSignalArgumentCaptor.getValue())); + } + + @Test + void signalServiceFlowExceptionSaveTest() { + Signal signalToSave = getSignal(); + signalToSave.setId(null); + + Mockito + .when(signalRepository.findByIndexSignalAndEserviceId(signalToSave.getSignalId(), signalToSave.getEserviceId())) + .thenReturn(Mono.just(signalToSave)); + + Mockito + .when(signalRepository.save(signalToSave)) + .thenThrow(new IllegalArgumentException()); + + signalServiceImpl.signalServiceFlow(signalToSave) + .onErrorResume(exception -> { + Assertions.assertNotNull(exception); + assertEquals(IllegalArgumentException.class, exception.getClass()); + return Mono.empty(); + }) + .block(); + } + + + private Signal getSignal() { + Signal signal = new Signal(); + signal.setId(this.id); + signal.setSignalId(this.signalId); + signal.setSignalType(this.signalType); + signal.setObjectType(this.objectType); + signal.setCorrelationId(this.correlationId); + signal.setEserviceId(this.eserviceId); + signal.setObjectId(this.objectId); + return signal; + } + + private void setUp() { + this.id = 0L; + this.signalId = 100L; + this.objectId = "OBJ1"; + this.correlationId = "0A"; + this.eserviceId = "OBJ1"; + this.objectType = "ESERVICE"; + this.signalType = SignalType.CREATE.toString(); + } +} \ No newline at end of file diff --git a/src/test/java/it/pagopa/interop/signalhub/persister/utility/UtilityTest.java b/src/test/java/it/pagopa/interop/signalhub/persister/utility/UtilityTest.java new file mode 100644 index 0000000..bebb6ff --- /dev/null +++ b/src/test/java/it/pagopa/interop/signalhub/persister/utility/UtilityTest.java @@ -0,0 +1,46 @@ +package it.pagopa.interop.signalhub.persister.utility; + +import it.pagopa.interop.signalhub.persister.exception.PDNDGenericException; +import it.pagopa.interop.signalhub.persister.queue.model.SignalEvent; +import it.pagopa.interop.signalhub.persister.queue.model.SignalType; +import it.pagopa.interop.signalhub.persister.utils.Utility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import static it.pagopa.interop.signalhub.persister.exception.ExceptionTypeEnum.MAPPER_ERROR; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +class UtilityTest { + + @Test + void jsonToObjectTest() { + String jsonNode = """ + { + "signalType": "CREATE", + "objectId": "OBJ1", + "objectType": "T1", + "eserviceId": "E1", + "signalId": "000001" + } + """; + + SignalEvent signalEvent = Utility.jsonToObject(jsonNode, SignalEvent.class); + assertNotNull(signalEvent); + assertEquals(SignalType.CREATE, signalEvent.getSignalType()); + assertEquals("OBJ1", signalEvent.getObjectId()); + assertEquals("T1", signalEvent.getObjectType()); + assertEquals("E1", signalEvent.getEserviceId()); + assertEquals("1", signalEvent.getSignalId().toString()); + } + + @Test + void jsonToObjectExceptionErrorTest() { + String jsonNode = "@sa1e43r@dgr@°#cv-dsw?!"; + PDNDGenericException exception = + Assertions.assertThrows(PDNDGenericException.class, + () -> Utility.jsonToObject(jsonNode, SignalEvent.class)); + + assertEquals(MAPPER_ERROR.getMessage(), exception.getMessage()); + } +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..8326c0e --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,24 @@ +cloud.aws.region.auto=true + +# Load instance profile credentials +cloud.aws.credentials.instanceProfile=true + +aws.sqs-endpoint=http://localhost:4566/000000000000/ +aws.internal-queue-name=local-internal-signal-hub +aws.region=us-east-1 +aws.profile=default + +spring.r2dbc.url=r2dbc:h2:mem:///db;MODE=PostgreSQL +spring.r2dbc.username=signal-hub-test +spring.r2dbc.password=signal-hub-test +spring.r2dbc.pool.max-size=20 +spring.r2dbc.pool.initial-size=5 +spring.r2dbc.pool.enabled=true +spring.data.r2dbc.repositories.enabled=true +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.sql.init.platform=h2 +spring.sql.init.mode=always + +spring.liquibase.enabled=false \ No newline at end of file diff --git a/src/test/resources/data-h2.sql b/src/test/resources/data-h2.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..ca6ee9c --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/src/test/resources/schema-h2.sql b/src/test/resources/schema-h2.sql new file mode 100644 index 0000000..6572b2a --- /dev/null +++ b/src/test/resources/schema-h2.sql @@ -0,0 +1,73 @@ +CREATE TABLE IF NOT EXISTS ESERVICE ( + eservice_id VARCHAR (255) NOT NULL, + producer_id VARCHAR (255) NOT NULL, + descriptor_id VARCHAR (255) NOT NULL, + event_id BIGINT, + state VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + tmst_last_edit TIMESTAMP, + UNIQUE (eservice_id, producer_id, descriptor_id), + PRIMARY KEY (eservice_id, producer_id, descriptor_id) +); + + +CREATE TABLE IF NOT EXISTS CONSUMER_ESERVICE ( + agreement_id VARCHAR (255) NOT NULL, + eservice_id VARCHAR (255) NOT NULL, + consumer_id VARCHAR (255) NOT NULL, + descriptor_id VARCHAR (255) NOT NULL, + event_id BIGINT, + state VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + tmst_last_edit TIMESTAMP, + UNIQUE (eservice_id, consumer_id, descriptor_id), + PRIMARY KEY (eservice_id, consumer_id, descriptor_id) +); + + +CREATE TABLE IF NOT EXISTS SIGNAL ( + id SERIAL PRIMARY KEY, + correlation_id VARCHAR(255) NOT NULL, + signal_id BIGINT NOT NULL, + object_id VARCHAR (255) NOT NULL, + eservice_id VARCHAR (255) NOT NULL, + object_type VARCHAR (255) NOT NULL, + signal_type VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE (signal_id, eservice_id) +); + + +CREATE TABLE IF NOT EXISTS DEAD_SIGNAL ( + id SERIAL PRIMARY KEY, + correlation_id VARCHAR(255) NOT NULL, + signal_id BIGINT NOT NULL, + object_id VARCHAR (255) NOT NULL, + eservice_id VARCHAR (255) NOT NULL, + object_type VARCHAR (255) NOT NULL, + signal_type VARCHAR (255) NOT NULL, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + error_reason VARCHAR(255) NOT NULL +); + + +CREATE TABLE IF NOT EXISTS TRACING_BATCH ( + batch_id SERIAL PRIMARY KEY, + state VARCHAR (255) NOT NULL, + type VARCHAR (50) NOT NULL, + last_event_id BIGINT, + tmst_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + +CREATE TABLE IF NOT EXISTS DEAD_EVENT ( + event_tmp_id SERIAL PRIMARY KEY, + tmst_insert TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + error_reason VARCHAR(255) NOT NULL, + event_id BIGINT NOT NULL, + event_type VARCHAR (255) NOT NULL, + object_type VARCHAR (255) NOT NULL, + descriptor_id VARCHAR (255), + eservice_id VARCHAR (255), + agreement_id VARCHAR (255) +); \ No newline at end of file diff --git a/src/test/resources/testcontainers/credentials b/src/test/resources/testcontainers/credentials new file mode 100644 index 0000000..4ab1290 --- /dev/null +++ b/src/test/resources/testcontainers/credentials @@ -0,0 +1,4 @@ +[default] +aws_access_key_id=TEST +aws_secret_access_key=TEST +region=us-east-1 diff --git a/src/test/resources/testcontainers/init.sh b/src/test/resources/testcontainers/init.sh new file mode 100644 index 0000000..efad9f3 --- /dev/null +++ b/src/test/resources/testcontainers/init.sh @@ -0,0 +1,15 @@ +echo "### CREATE SIGNAL HUB PERSISTER QUEUES ###" + +queues="internal-queue" + +for qn in $( echo $queues | tr " " "\n" ) ; do + + echo creating queue $qn ... + + aws --profile default --region us-east-1 --endpoint-url http://localstack:4566 \ + sqs create-queue \ + --attributes '{"DelaySeconds":"2"}' \ + --queue-name $qn +done + +echo "Initialization terminated"