Compare commits

..

1 Commits

Author SHA1 Message Date
MSI\joaly
4549f45f58 ADD project from GitLab Banesco 2025-12-16 12:00:17 -04:00
231 changed files with 9808 additions and 1 deletions

View File

@ -1,3 +1,3 @@
# BIAN-Quarkus
Repositorio de APIs BIAN implementadas con Quarkus
Repositorio de APIs BIAN de consulta de productos. Con implementación del mock para la consulta de TDD.

View File

@ -0,0 +1,5 @@
*
!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/*

View File

@ -0,0 +1,45 @@
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
.flattened-pom.xml
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
# Plugin directory
/.quarkus/cli/plugins/
# TLS Certificates
.certs/

View File

@ -0,0 +1 @@
maven-wrapper.jar

View File

@ -0,0 +1,93 @@
/*
* 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.
*/
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.ThreadLocalRandom;
public final class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "3.3.2";
private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
public static void main(String[] args) {
log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION);
if (args.length != 2) {
System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing");
System.exit(1);
}
try {
log(" - Downloader started");
final URL wrapperUrl = URI.create(args[0]).toURL();
final String jarPath = args[1].replace("..", ""); // Sanitize path
final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
downloadFileFromURL(wrapperUrl, wrapperJarPath);
log("Done");
} catch (IOException e) {
System.err.println("- Error downloading: " + e.getMessage());
if (VERBOSE) {
e.printStackTrace();
}
System.exit(1);
}
}
private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath)
throws IOException {
log(" - Downloading to: " + wrapperJarPath);
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
final String username = System.getenv("MVNW_USERNAME");
final char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
Path temp = wrapperJarPath
.getParent()
.resolve(wrapperJarPath.getFileName() + "."
+ Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
try (InputStream inStream = wrapperUrl.openStream()) {
Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING);
Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING);
} finally {
Files.deleteIfExists(temp);
}
log(" - Downloader complete");
}
private static void log(String msg) {
if (VERBOSE) {
System.out.println(msg);
}
}
}

View File

@ -0,0 +1,20 @@
# 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.
wrapperVersion=3.3.2
distributionType=source
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar

View File

@ -0,0 +1,10 @@
# Changelog
Todos los cambios notables en este proyecto se documentarán en este archivo.
El formato se basa en [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
y este proyecto se adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v1.0.0 - UNRELEASED
### Updated
- Migración completada a Quarkus 3.25.3

View File

@ -0,0 +1,61 @@
# bus-evaluate-customer-product
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
## Running the application in dev mode
You can run your application in dev mode that enables live coding using:
```shell script
./mvnw quarkus:dev
```
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at <http://localhost:8080/q/dev/>.
## Packaging and running the application
The application can be packaged using:
```shell script
./mvnw package
```
It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory.
Be aware that its not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory.
The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.
If you want to build an _über-jar_, execute the following command:
```shell script
./mvnw package -Dquarkus.package.jar.type=uber-jar
```
The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`.
## Creating a native executable
You can create a native executable using:
```shell script
./mvnw package -Dnative
```
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
```shell script
./mvnw package -Dnative -Dquarkus.native.container-build=true
```
You can then execute your native executable with: `./target/bus-evaluate-customer-product-1.0.0-runner`
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/maven-tooling>.
## Related Guides
- Hibernate Validator ([guide](https://quarkus.io/guides/validation)): Validate object properties (field, getter) and method parameters for your beans (REST, CDI, Jakarta Persistence)
- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it
- YAML Configuration ([guide](https://quarkus.io/guides/config-yaml)): Use YAML to configure your Quarkus application
- SmallRye Health ([guide](https://quarkus.io/guides/smallrye-health)): Monitor service health

View File

@ -0,0 +1,2 @@
- user login de spring tiene respuesta diferente a la hecha en quarkus (migracion cargo en cuenta)
- Condicionar que el postgres guarde en cache

332
bus-evaluate-customer-product/mvnw vendored Normal file
View File

@ -0,0 +1,332 @@
#!/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.3.2
#
# 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." >&2
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." >&2
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" >&2
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.3.2/maven-wrapper-3.3.2.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.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." >&2
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
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 "$@"

206
bus-evaluate-customer-product/mvnw.cmd vendored Normal file
View File

@ -0,0 +1,206 @@
@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.3.2
@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. >&2
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. >&2
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo. >&2
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. >&2
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.3.2/maven-wrapper-3.3.2.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.3.2/maven-wrapper-3.3.2.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 "&{"^
"Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Error '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%

View File

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.banesco</groupId>
<artifactId>bus-evaluate-customer-product</artifactId>
<name>bus-evaluate-customer-product</name>
<description>API REST Customer Product (consulta de cuentas) BUS</description>
<version>1.0.0</version>
<properties>
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.25.3</quarkus.platform.version>
<skipITs>true</skipITs>
<quarkus.main-class>com.banesco.Main</quarkus.main-class>
<surefire-plugin.version>3.5.3</surefire-plugin.version>
<cxf.version>4.0.4</cxf.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.banesco</groupId>
<artifactId>commons</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.cxf</groupId>
<artifactId>quarkus-cxf</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<!-- postgresql -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
<goal>native-image-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/PruebaService.wsdl</wsdl>
<extraargs>
<extraarg>-client</extraarg>
<extraarg>-verbose</extraarg>
</extraargs>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/cxf</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,12 @@
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
RUN mkdir -p /work
ENV TZ="America/Caracas"
ENV LANGUAGE='en_US:en'
VOLUME /tmp
COPY /file/*-runner /work/busCustomerAccounts
RUN chmod -R 775 /work
RUN ls -ltra /work/
EXPOSE 8080
WORKDIR /work/
ENTRYPOINT ["./busCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"]

View File

@ -0,0 +1,98 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/pub-get-accounts-client-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi9/openjdk-17:1.23
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@ -0,0 +1,94 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/pub-get-accounts-client-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi9/openjdk-17:1.23
ENV LANGUAGE='en_US:en'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@ -0,0 +1,29 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/pub-get-accounts-client .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client
#
# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.6` base image is based on UBI 9.
# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
###
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.6
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@ -0,0 +1,32 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/pub-get-accounts-client .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client
#
# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
###
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@ -0,0 +1,12 @@
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.QuarkusMain;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@QuarkusMain
public class Main {
public static void main(String[] args) {
log.info("Running bus-evaluate-customer-product...");
Quarkus.run(args);
}
}

View File

@ -0,0 +1,12 @@
package application.exception;
public class GatewayServiceException extends RuntimeException {
public GatewayServiceException(String message) {
super(message);
}
public GatewayServiceException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,7 @@
package application.exception;
public class InvalidTokenException extends RuntimeException {
public InvalidTokenException(String message) {
super(message);
}
}

View File

@ -0,0 +1,7 @@
package application.exception;
public class ServiceCommunicationException extends GatewayServiceException {
public ServiceCommunicationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,37 @@
package application.factory;
import application.port.input.command.RegisterSecurityCommand;
import domain.dto.response.StatusResponse;
import domain.model.AccountClientContext;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
import java.sql.Timestamp;
import java.time.LocalDateTime;
@Slf4j
@ApplicationScoped
public class SecurityTraceFactory {
public RegisterSecurityCommand createCommandFromContext(AccountClientContext context, StatusResponse statusResponse) {
var request = context.getRequest();
var customerReference = request.getCustomerPositionState().getCustomerReference();
var device = request.getDevice();
long durationInMillis = System.currentTimeMillis() - context.getStartExecution();
return RegisterSecurityCommand.builder()
.login(context.getUsername())
.fecHor(Timestamp.valueOf(LocalDateTime.now()))
.nacCli(customerReference.getCustomerIdType())
.cedRifCli(Integer.valueOf(customerReference.getCustomerId()))
.tipoRespuesta(Integer.valueOf(statusResponse.getStatusCode()))
.msgRespuesta(statusResponse.getMessage())
.codFintech(context.getFintechId())
.tiempoRespuesta((int) durationInMillis)
.tipoDispositivo(device.getDeviceType())
.desDispositivo(device.getDeviceDescription())
.ipCli(device.getDeviceIp())
.build();
}
}

View File

@ -0,0 +1,25 @@
package application.helper;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
@Slf4j
public class CustomerHelper {
/**
* Construye de forma segura un ID de cliente a partir de su tipo y número.
*
* @param idType El tipo de identificación (ej. "V", "J").
* @param idNumber El número de identificación.
* @return Un {@link Optional} con el ID concatenado si ambos parámetros son válidos (no nulos ni vacíos),
* o un {@code Optional.empty()} en caso contrario.
*/
public static Optional<String> buildCustomerId(String idType, String idNumber) {
return Optional.ofNullable(idType)
.filter(type -> !type.isBlank())
.flatMap(type -> Optional.ofNullable(idNumber)
.filter(number -> !number.isBlank())
.map(number -> type + number));
}
}

View File

@ -0,0 +1,79 @@
package application.helper;
import domain.dto.response.CustomerPositionStateResponse;
import domain.dto.response.CustomerResponse;
import domain.dto.response.StatusResponse;
import domain.model.BankingProduct;
import domain.model.ErrorCode;
import infrastructure.service.StatusDescriptionResolver;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.Response;
import java.util.List;
@ApplicationScoped
public class ResponseHelper {
private final StatusDescriptionResolver statusDescriptionResolver;
public ResponseHelper(StatusDescriptionResolver statusDescriptionResolver) {
this.statusDescriptionResolver = statusDescriptionResolver;
}
public Response buildErrorResponse(String errorCodeKey, String requestId) {
var errorCode = statusDescriptionResolver.getCode(errorCodeKey);
return this.buildErrorResponse(errorCode, requestId);
}
/**
* Construye una respuesta de error estandarizada de forma genérica.
* @param errorCode Objeto con el código de error interno.
* @return Un objeto Response estándar de JAX-RS.
*/
public Response buildErrorResponse(ErrorCode errorCode, String requestId) {
var statusResponse = StatusResponse.builder()
.status(errorCode.status())
.statusCode(errorCode.statusCode())
.message(errorCode.description())
.traceId(requestId)
.build();
var response = new CustomerResponse(statusResponse);
int httpStatus = errorCode.getHttpStatusCode();
return Response.status(httpStatus)
.entity(response)
.build();
}
/**
* Construye una respuesta de éxito (200) CON DATOS.
* @param customerAccounts La lista de productos (el cuerpo de la respuesta).
* @param successCode El ErrorCode "200" (para los detalles del status).
* @param requestId El ID de traza.
* @return Un objeto Response estándar de JAX-RS.
*/
public Response buildSuccessResponse(List<BankingProduct> customerAccounts, ErrorCode successCode, String requestId) {
var status = StatusResponse.builder()
.status(successCode.status())
.statusCode(successCode.statusCode())
.message(successCode.description())
.traceId(requestId)
.build();
var accounts = CustomerPositionStateResponse.builder()
.bankingProducts(customerAccounts)
.record(customerAccounts.size())
.build();
var response = new CustomerResponse(status, accounts);
return Response.status(successCode.getHttpStatusCode())
.entity(response)
.build();
}
}

View File

@ -0,0 +1,7 @@
package application.port.input;
import domain.entity.AppConfiguration;
public interface IAppConfig {
AppConfiguration execute(String appName);
}

View File

@ -0,0 +1,13 @@
package application.port.input;
import application.port.input.command.CustomerAccountCommand;
import domain.entity.AppConfiguration;
import domain.model.AccountClientContext;
import domain.model.BankingProduct;
import java.util.List;
public interface ICustomerProduct {
// TODO: mejorar
List<BankingProduct> execute(CustomerAccountCommand command, AccountClientContext context, AppConfiguration appConfig, String otp, Boolean isCacheable, String requestId);
}

View File

@ -0,0 +1,7 @@
package application.port.input;
import application.port.input.command.EncodeValueCommand;
public interface IEncodeValue {
String execute(EncodeValueCommand command, String requestId);
}

View File

@ -0,0 +1,7 @@
package application.port.input;
import application.port.input.command.RegisterSecurityCommand;
public interface IRegisterSecurity {
void execute(RegisterSecurityCommand command, String requestId);
}

View File

@ -0,0 +1,7 @@
package application.port.input;
import application.port.input.command.ServiceStatusCommand;
public interface IServiceStatus {
Boolean execute(ServiceStatusCommand command, String requestId);
}

View File

@ -0,0 +1,13 @@
package application.port.input;
import application.port.input.command.UserLoginCommand;
import domain.model.UserLogin;
import java.util.Optional;
/**
* Puerto de entrada para el caso de uso de login de usuario (API privada)
*/
public interface IUserLogin {
Optional<UserLogin> execute(UserLoginCommand command, String requestId);
}

View File

@ -0,0 +1,7 @@
package application.port.input;
import application.port.input.command.ValidateOtpCommand;
public interface IValidateOtp {
Boolean isValidOtp(ValidateOtpCommand command, String requestId);
}

View File

@ -0,0 +1,7 @@
package application.port.input;
import application.port.input.command.ValidateProfileCommand;
public interface IValidateProfile {
Boolean execute(ValidateProfileCommand command, String requestId);
}

View File

@ -0,0 +1,25 @@
package application.port.input.command;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class CustomerAccountCommand {
String rif;
String bankCode;
String currencyCode;
String status;
String statusMe;
String productCv;
String product;
String channel;
String service;
String operator;
String phone;
String affiliationStatus;
String counterpartyAccount;
String holder;
}

View File

@ -0,0 +1,5 @@
package application.port.input.command;
public record EncodeValueCommand(
String sApp
) {}

View File

@ -0,0 +1,39 @@
package application.port.input.command;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
import java.util.Date;
@Value
@Builder
@RegisterForReflection
public class RegisterSecurityCommand {
String login;
Date fecHor;
String nacCli;
Integer cedRifCli;
String tipoProductoCli;
String tipoProductoBen;
String productoCli;
String codEmpresa;
String nacBen;
Integer cedBen;
String nombreBen;
String productoBen;
Double monto;
String referencia;
String nroDePago;
String desPago;
String objeto;
Integer tipoRespuesta;
String msgRespuesta;
Integer tiempoRespuesta;
String codFintech;
String cedRifFintech;
String tipoDispositivo;
String desDispositivo;
String ipCli;
String sp;
}

View File

@ -0,0 +1,9 @@
package application.port.input.command;
import domain.model.BankService;
public record ServiceStatusCommand(
String applicationId,
String transactionId,
BankService bankService
) { }

View File

@ -0,0 +1,14 @@
package application.port.input.command;
import io.quarkus.runtime.annotations.RegisterForReflection;
/**
* Contiene los datos entradas para el caso de uso de login de usuario
* @param customerId
* @param customerIdType
*/
@RegisterForReflection
public record UserLoginCommand(
String customerId,
String customerIdType
) { }

View File

@ -0,0 +1,11 @@
package application.port.input.command;
public record ValidateOtpCommand(
String otpToken,
String appOrigen,
String login,
String ipAddress,
String canal,
String servicio,
String domain
) { }

View File

@ -0,0 +1,10 @@
package application.port.input.command;
/**
* Contiene los datos de entrada para el caso de uso de validate profile
*/
public record ValidateProfileCommand(
String customerId,
String ipAddress,
String hash
) {}

View File

@ -0,0 +1,5 @@
package application.port.output;
public interface IAppConfigRepository {
}

View File

@ -0,0 +1,5 @@
package application.port.output;
public interface IMaintenanceCheckPort {
Boolean isUnderMaintenance(String requestId);
}

View File

@ -0,0 +1,35 @@
package application.port.output;
import application.exception.GatewayServiceException;
import application.port.input.IRegisterSecurity;
import application.port.input.command.RegisterSecurityCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IRegisterSecurityPort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class RegisterSecurityUseCase implements IRegisterSecurity {
private final IRegisterSecurityPort registerSecurityPort;
@Inject
public RegisterSecurityUseCase(IRegisterSecurityPort registerSecurityPort) {
this.registerSecurityPort = registerSecurityPort;
}
@Override
public void execute(RegisterSecurityCommand command, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Registrar traza de seguridad"));
try {
registerSecurityPort.writeTrace(command, requestId);
} catch (GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de register security"));
throw ex;
} finally {
log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: Registrar traza de seguridad"));
}
}
}

View File

@ -0,0 +1,43 @@
package application.service;
import com.banesco.common.domain.model.redis.UserAccountRedis;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import com.banesco.common.infraestructure.repository.UserAccountRedisRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@ApplicationScoped
public class AccountService {
@Inject
UserAccountRedisRepository userAccountRedisRepository;
/**
* Guarda una lista de cuentas en la caché de Redis y establece un tiempo de expiración (TTL).
*
* @param requestId ID de la solicitud para trazabilidad en los logs.
* @param id El identificador único del cliente para la clave del hash.
* @param sid El identificador de sesión para la clave del índice.
* @param accounts La lista de cuentas a guardar.
* @param ttlInSeconds El tiempo de vida (en segundos) para las claves en caché.
*/
public void saveAccounts(String requestId, String id, String sid, List<UserAccountRedis> accounts, long ttlInSeconds) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando guardado en Redis para el sid", sid));
if (accounts == null || accounts.isEmpty()) {
log.warn(LoggerHelper.buildInfoPrivateRequest(requestId, "Se intento guardar una lista de cuentas vacia o nula. No se realiza la operacion."));
return;
}
try {
userAccountRedisRepository.saveOrUpdate(sid, accounts);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Guardado exitoso en Redis para el sid", sid));
} catch (Exception ex) {
log.error(LoggerHelper.buildError(requestId, String.format("Error al intentar guardar en Redis para el sid %s. %s", sid, ex.getMessage())));
}
}
}

View File

@ -0,0 +1,67 @@
package application.service;
import application.port.input.IValidateOtp;
import application.port.input.command.ValidateOtpCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import infrastructure.config.DomainConfig;
import infrastructure.config.OtpConfig;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class OtpValidationService {
private final IValidateOtp validateOtpUseCase;
private final OtpConfig otpConfig;
private final DomainConfig domainConfig;
@Inject
public OtpValidationService(
IValidateOtp validateOtpUseCase,
OtpConfig otpConfig,
DomainConfig domainConfig
) {
this.validateOtpUseCase = validateOtpUseCase;
this.otpConfig = otpConfig;
this.domainConfig = domainConfig;
}
/**
* Punto de entrada principal para validar un OTP.
*
* @return {@code true} si el OTP es válido, de lo contrario {@code false}.
*/
public boolean isOtpValid(
String otpToken,
String originApp,
String login,
String ipAddress,
String requestId
) {
return validateOtpFromApiService(otpToken, originApp, login, ipAddress, requestId);
}
private boolean validateOtpFromApiService(
String otpToken,
String originApp,
String login,
String ipAddress,
String requestId
) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Validando OTP usando servicio externo"));
var otpCommand = new ValidateOtpCommand(
otpToken,
originApp,
login,
ipAddress,
otpConfig.channel(),
otpConfig.servicio(),
domainConfig.payment()
);
return validateOtpUseCase.isValidOtp(otpCommand, requestId);
}
}

View File

@ -0,0 +1,63 @@
package application.service;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import com.banesco.common.infraestructure.repository.PostgresRedisRepository;
import domain.entity.AppConfiguration;
import infrastructure.mapper.PostgresMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
@Slf4j
@ApplicationScoped
public class PostgresConfigService {
@Inject
PostgresRedisRepository postgresRedisRepository;
/**
* Recibe una entidad AppConfiguration de la lógica de negocio y la mapea
* a un CustomerAccountPostgresConfig del "commons" para guardarla en Redis.
*/
public void savePostgresConfig(String configName, AppConfiguration appConfigItem, long ttlInSeconds, String requestId) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando guardado de configuracion postgres en Redis", appConfigItem.toString()));
try {
var commonConfigToSave = PostgresMapper.mapToCommon(appConfigItem);
postgresRedisRepository.save(configName, commonConfigToSave, ttlInSeconds);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Guardado exitoso en Redis. Clave: " + configName));
} catch (Exception ex) {
log.error(LoggerHelper.buildError(requestId, "Error al intentar guardar configuracion de postgres en Redis"), ex);
}
}
/**
* Busca una configuración en Redis por su nombre (clave).
*
* @param configName La clave de Redis (ej: el appId).
* @param requestId El ID de la traza para logging.
* @return Un Optional con la entidad AppConfiguration si se encuentra en caché.
*/
public Optional<AppConfiguration> findByName(String configName, String requestId) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Buscando configuracion en Redis. Clave: " + configName));
try {
var commonConfigOpt = postgresRedisRepository.findByName(configName);
if (commonConfigOpt.isEmpty()) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Cache miss para clave: " + configName));
return Optional.empty();
}
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Cache hit para clave: " + configName));
return Optional.of(PostgresMapper.mapToApp(commonConfigOpt.get()));
} catch (Exception ex) {
log.error(LoggerHelper.buildError(requestId, "Error al intentar leer configuracion de postgres en Redis"), ex);
// Si hay error en Redis, es mejor tratarlo como un "cache miss" (no encontrado)
return Optional.empty();
}
}
}

View File

@ -0,0 +1,84 @@
package application.usecase;
import application.exception.GatewayServiceException;
import application.port.input.ICustomerProduct;
import application.port.input.command.CustomerAccountCommand;
import application.service.AccountService;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.entity.AppConfiguration;
import domain.model.AccountClientContext;
import domain.model.BankingProduct;
import domain.repository.ICustomerProductPort;
import infrastructure.mapper.BankingProductMapper;
import infrastructure.mapper.CacheMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.util.List;
@Slf4j
@ApplicationScoped
public class CustomerProductUseCase implements ICustomerProduct {
private final ICustomerProductPort customerProductPort;
private final AccountService accountService;
@ConfigProperty(name = "app.redis.expiration")
long cacheTtl;
@Inject
public CustomerProductUseCase(
ICustomerProductPort customerProductPort,
AccountService accountService
) {
this.customerProductPort = customerProductPort;
this.accountService = accountService;
}
@Override
public List<BankingProduct> execute(CustomerAccountCommand command, AccountClientContext context, AppConfiguration appConfig, String otp, Boolean isCacheable, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: api-get-db2-accounts-client-v2"));
try {
var customerAccountsFromService = customerProductPort.execute(command, requestId);
// Si el servicio no devuelve nada, no hay nada que guardar ni devolver.
if (customerAccountsFromService == null || customerAccountsFromService.isEmpty()) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio principal no devolvio cuentas para requestId {}", requestId));
return List.of();
}
var bankingProducts = BankingProductMapper.fromCustomerAccountList(customerAccountsFromService, appConfig.isEncryptionData());
if(isCacheable) {
var accountsToCache = CacheMapper.toUserAccountRedisList(
customerAccountsFromService,
context.getRedisKey(),
context.getFinalCustomerId(),
otp
);
log.info(LoggerHelper.buildInfoPrivateRequest(
requestId, String.format("Guardando %s cuentas en cache", accountsToCache.size())
));
accountService.saveAccounts(
requestId,
context.getFinalCustomerId(),
context.getRedisKey(),
accountsToCache,
cacheTtl
);
}
return bankingProducts;
} catch (GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de api-get-db2-accounts-client-v2"));
throw ex;
} finally {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: api-get-db2-accounts-client-v2"));
}
}
}

View File

@ -0,0 +1,36 @@
package application.usecase;
import application.exception.GatewayServiceException;
import application.port.input.IEncodeValue;
import application.port.input.command.EncodeValueCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IEncodeValuePort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class EncodeValueUseCase implements IEncodeValue {
private final IEncodeValuePort encodeValuePort;
@Inject
public EncodeValueUseCase(IEncodeValuePort encodeValuePort) {
this.encodeValuePort = encodeValuePort;
}
@Override
public String execute(EncodeValueCommand command, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Encode Value"));
try {
return encodeValuePort.execute(command, requestId);
} catch (GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de Encode Value: " + ex.getMessage()));
throw ex;
} finally {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Encode Value"));
}
}
}

View File

@ -0,0 +1,28 @@
package application.usecase;
import application.port.input.IAppConfig;
import domain.entity.AppConfiguration;
import domain.repository.AppConfigRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class GetAppConfigUseCase implements IAppConfig {
private final AppConfigRepository appConfigRepository;
@Inject
public GetAppConfigUseCase(AppConfigRepository appConfigRepository) {
this.appConfigRepository = appConfigRepository;
}
public AppConfiguration execute(String appName) {
return appConfigRepository.findConfigByAppName(appName)
.orElseThrow(() -> new NotFoundException("Configuracion no encontrada para la app: " + appName));
}
}

View File

@ -0,0 +1,72 @@
package application.usecase;
import application.exception.GatewayServiceException;
import application.port.input.IServiceStatus;
import application.port.input.command.ServiceStatusCommand;
import application.port.output.IMaintenanceCheckPort;
import com.banesco.common.infraestructure.helpers.JsonHelper;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IStatusPort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class ServiceStatusUseCase implements IServiceStatus {
private static final String INACTIVE_STATUS = "I";
private final IStatusPort statusApiPort;
private final IMaintenanceCheckPort maintenancePort;
@Inject
public ServiceStatusUseCase(
IStatusPort statusApiService,
IMaintenanceCheckPort maintenancePort
) {
this.statusApiPort = statusApiService;
this.maintenancePort = maintenancePort;
}
/**
* Determina el estado de disponibilidad de un servicio, devolviendo true si está activo y false si no lo está.
*
* @param command El objeto de comando que contiene los datos necesarios para la consulta, como el ID de aplicación y transacción.
* @param requestId Un identificador único de la solicitud, utilizado principalmente para la trazabilidad en los logs.
* @return {@code true} si el servicio se considera activo; {@code false} si está inactivo, en mantenimiento o si la verificación falló.
*/
@Override
public Boolean execute(ServiceStatusCommand command, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Service Status"));
// Si no estamos en ventana de mantenimiento, el servicio está activo.
if(!maintenancePort.isUnderMaintenance(requestId)) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "No estamos en ventana de mantenimiento. Servicio ACTIVO"));
return true;
}
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "En ventana de mantenimiento, consultando estado remoto..."));
try {
// Si estamos en horario de validacion, consultamos el estado remoto.
var remoteStatus = statusApiPort.getServiceStatus(command, requestId);
var isActive = remoteStatus != null && !INACTIVE_STATUS.equalsIgnoreCase(remoteStatus.getStatus());
if (isActive) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio externo confirmo estado ACTIVO"));
} else {
log.warn(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio externo esta nulo o en estado INACTIVO"));
}
return isActive;
} catch (GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de service status"));
throw ex;
} finally {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Service Status"));
}
}
}

View File

@ -0,0 +1,52 @@
package application.usecase;
import application.exception.GatewayServiceException;
import application.port.input.IUserLogin;
import application.port.input.command.UserLoginCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.model.UserLogin;
import domain.repository.IUserLoginPort;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
@Slf4j
@ApplicationScoped
public class UserLoginUseCase implements IUserLogin {
private final IUserLoginPort userLoginApiPort;
public UserLoginUseCase(IUserLoginPort userLoginApiPort) {
this.userLoginApiPort = userLoginApiPort;
}
/**
* Orquesta la ejecución del caso de uso de login de usuario
* Encapsula la lógica de negocio, incluyendo la generación de códigos de referencia
* y el manejo de fallos de comunicación con el servicio externo.
* @param command El comando con los datos de entrada para el login.
* @param requestId El ID de la solicitud para trazabilidad.
* @return Un {@link Optional} con el objeto {@link UserLogin} si el login fue exitoso,
* o un {@code Optional.empty()} si falló por cualquier motivo.
*/
@Override
public Optional<UserLogin> execute(UserLoginCommand command, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: User Login"));
try {
var result = userLoginApiPort.execute(command, requestId);
if(result == null) {
log.error(LoggerHelper.buildError(requestId, " El login no fue exitoso segun la respuesta del servicio"));
return Optional.empty();
}
log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: User Login"));
return Optional.of(result);
} catch (GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de User Login"));
throw ex;
}
}
}

View File

@ -0,0 +1,57 @@
package application.usecase;
import application.exception.GatewayServiceException;
import application.exception.InvalidTokenException;
import application.port.input.IValidateOtp;
import application.port.input.command.ValidateOtpCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IOtpPort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.jboss.resteasy.reactive.RestResponse;
@Slf4j
@ApplicationScoped
public class ValidateOtpUseCase implements IValidateOtp {
private final IOtpPort otpApiPort;
@Inject
public ValidateOtpUseCase(IOtpPort otpApiService) {
this.otpApiPort = otpApiService;
}
/**
* Orquesta la validación de un token OTP.
* Encapsula la lógica de negocio para determinar si un OTP es válido,
* manejando también los fallos de comunicación con el servicio externo.
*
* @param command El comando con los datos necesarios para validar el OTP.
* @param requestId El ID de la solicitud para trazabilidad.
* @return {@code true} si el servicio externo responde con un status HTTP 200 (OK),
* {@code false} en cualquier otro caso (respuesta de error o fallo de comunicación).
*/
@Override
public Boolean isValidOtp(ValidateOtpCommand command, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Validate OTP"));
try {
var apiResponse = otpApiPort.validateOtp(command, requestId);
var isValid = apiResponse.getStatus() == RestResponse.Status.OK.getStatusCode();
log.info("Validacion de OTP para requestId {}. Resultado: {}. Codigo de estado recibido: {}",
requestId, isValid, apiResponse.getStatus());
return isValid;
} catch(InvalidTokenException ex) {
return false;
} catch(GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de validate otp"));
throw ex;
} finally {
log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: Validate OTP"));
}
}
}

View File

@ -0,0 +1,81 @@
package application.usecase;
import application.exception.GatewayServiceException;
import application.port.input.IValidateProfile;
import application.port.input.command.ValidateProfileCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.model.ClientProfile;
import domain.repository.IValidateProfilePort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.Optional;
@Slf4j
@ApplicationScoped
public class ValidateProfileUseCase implements IValidateProfile {
private final IValidateProfilePort validateProfilePort;
@Inject
public ValidateProfileUseCase(IValidateProfilePort validateProfilePort) {
this.validateProfilePort = validateProfilePort;
}
/**
* Orquesta la validación del perfil de un cliente.
* Determina si un perfil es válido según la respuesta de un servicio externo,
* navegando de forma segura a través de una estructura de datos anidada.
*
* @param command El comando con los datos de entrada para la validación.
* @param requestId El ID de la solicitud para trazabilidad.
* @return {@code true} si el perfil es válido (status = 0), {@code false} en cualquier otro caso.
*/
@Override
public Boolean execute(ValidateProfileCommand command, String requestId) {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Validate Profile"));
try {
var result = validateProfilePort.execute(command, requestId);
// La validación completa se realiza en una única cadena funcional y segura.
var isValidProfile = Optional.ofNullable(result)
.filter(profile -> !profile.getHasError())
.flatMap(this::extractStatusFromData)
.map(status -> status == 0)
.orElse(false);
log.info("{} - Validacion de perfil completada ¿Es valido?: {}", requestId, isValidProfile);
return isValidProfile;
} catch (GatewayServiceException ex) {
log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de Validate Profile"));
throw ex;
} finally {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Validate Profile"));
}
}
/**
* Helper privado para navegar de forma segura la estructura de Mapas anidada
* y extraer el valor de 'stausu'.
*
* @param profile El objeto ClientProfile que contiene los datos.
* @return Un Optional<Integer> con el valor de 'stausu' si se encuentra.
*/
@SuppressWarnings("unchecked")
private Optional<Integer> extractStatusFromData(ClientProfile profile) {
return Optional.ofNullable(profile.getData())
.filter(Map.class::isInstance).map(m -> (Map<String, Object>) m)
.map(data -> data.get("banesco"))
.filter(Map.class::isInstance).map(m -> (Map<String, Object>) m)
.map(banesco -> banesco.get("respuesta"))
.filter(Map.class::isInstance).map(m -> (Map<String, Object>) m)
.map(res -> res.get("respuesta"))
.filter(Map.class::isInstance).map(m -> (Map<String, Object>) m)
.map(respuesta -> respuesta.get("stausu"))
.filter(Integer.class::isInstance)
.map(Integer.class::cast);
}
}

View File

@ -0,0 +1,60 @@
package domain.dto.request;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
public class CustomerRequest {
@Valid
@NotNull(message = "VDE01")
private CustomerPositionState customerPositionState;
@Valid
@NotNull(message = "VDE01")
private Device device;
private TokenAssignmentInstance tokenAssignmentInstance;
@Data
public static class CustomerPositionState {
private CustomerReference customerReference;
@Data
public static class CustomerReference {
/**
* Valida que sea una única letra del conjunto [V, E, P, G, J].
*/
private String customerIdType;
/**
* Valida que sea numérico con un máximo de 9 dígitos.
*/
private String customerId;
}
}
@Data
public static class Device {
private String deviceType;
private String deviceDescription;
private String deviceIp;
private String deviceSessionReference;
}
@Data
public static class TokenAssignmentInstance {
private String tokenIdentificationCode;
}
}

View File

@ -0,0 +1,16 @@
package domain.dto.response;
import domain.model.BankingProduct;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
import java.util.List;
@Value
@Builder
@RegisterForReflection
public class CustomerPositionStateResponse {
int record;
List<BankingProduct> bankingProducts;
}

View File

@ -0,0 +1,23 @@
package domain.dto.response;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Getter;
@Getter
@RegisterForReflection
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomerResponse {
StatusResponse statusResponse;
CustomerPositionStateResponse customerPositionState;
public CustomerResponse(StatusResponse statusResponse, CustomerPositionStateResponse customerPositionState) {
this.statusResponse = statusResponse;
this.customerPositionState = customerPositionState;
}
public CustomerResponse(StatusResponse statusResponse) {
this.statusResponse = statusResponse;
}
}

View File

@ -0,0 +1,15 @@
package domain.dto.response;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class StatusResponse {
String status;
String statusCode;
String message;
String traceId;
}

View File

@ -0,0 +1,31 @@
package domain.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import domain.model.Configuration;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@RegisterForReflection
public class AppConfiguration {
@JsonIgnore
private Long appId;
private String appName;
private String apiName;
private String type;
private String event;
private boolean encryptionData;
private Configuration configuration;
}

View File

@ -0,0 +1,33 @@
package domain.entity;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@RegisterForReflection
public class ConfigItem {
boolean cacheable;
String bankCode;
String currencyCode;
String status;
String statusMe;
String productCv;
String product;
String channel;
String service;
String operator;
String phone;
String affiliationStatus;
String counterpartyAccount;
String holder;
String codProduct;
String statusOperation;
String statusRestriction;
String statusOperationRestriction;
public ConfigItem() {}
}

View File

@ -0,0 +1,41 @@
package domain.model;
import domain.dto.request.CustomerRequest;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import java.util.Optional;
@Data
@RegisterForReflection
public class AccountClientContext {
// Datos de entrada
final long startExecution;
final String requestId;
final CustomerRequest request;
final String appId, fintechId, finalCustomerId, redisKey;
String username;
Optional<ErrorCode> finalError = Optional.empty();
public AccountClientContext(long startExecution, String requestId, CustomerRequest request, String... headers) {
var customer = request.getCustomerPositionState().getCustomerReference();
var sid = request.getDevice().getDeviceSessionReference();
this.startExecution = startExecution;
this.requestId = requestId;
this.request = request;
this.appId = headers[0];
this.fintechId = headers[1];
this.finalCustomerId = customer.getCustomerIdType().concat(customer.getCustomerId());
this.username = Optional.ofNullable(headers[2]).orElse(this.finalCustomerId);
this.redisKey = Optional.ofNullable(sid)
.filter(s -> !s.isBlank())
.map(s -> this.finalCustomerId + sid)
.orElse(this.finalCustomerId);
}
}

View File

@ -0,0 +1,10 @@
package domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public record BankService(
String bankCode,
String serviceCode,
String eventCode
) {}

View File

@ -0,0 +1,17 @@
package domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
import java.math.BigDecimal;
@Value
@Builder
@RegisterForReflection
public class BankingProduct {
String productNumber;
String productType;
BigDecimal balance;
String currency;
}

View File

@ -0,0 +1,15 @@
package domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class ClientProfile {
Boolean hasError;
String errorMessage;
String errorCode;
Object data;
}

View File

@ -0,0 +1,19 @@
package domain.model;
import domain.entity.ConfigItem;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Map;
@Getter
@Setter
@ToString
@RegisterForReflection
public class Configuration {
private Map<String, Boolean> validationRules;
private Map<String, String> aliases;
private Map<String, ConfigItem> configuration;
}

View File

@ -0,0 +1,36 @@
package domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
import java.math.BigDecimal;
@Value
@Builder
@RegisterForReflection
public class CustomerAccount {
String banco;
String cuenta;
String producto;
String descProd;
String claseCta;
String tipoCta;
String moneda;
BigDecimal saldoDisponible;
String estatus;
String statusMe;
String titular;
BigDecimal maximumPerDay;
BigDecimal maximumPerTransaction;
BigDecimal maximumPerMonth;
BigDecimal maximumMonthlyFee;
BigDecimal minimumPerTransaction;
String operator;
String phone;
String channel;
String service;
String affiliationStatus;
String codeError;
String descError;
}

View File

@ -0,0 +1,28 @@
package domain.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.runtime.annotations.RegisterForReflection;
/**
* Representa un código de error de la aplicación.
*/
@RegisterForReflection
public record ErrorCode(
@JsonProperty("backend_code") String backendCode,
@JsonProperty("http_code") String httpCode,
@JsonProperty("status_code") String statusCode,
@JsonProperty("description") String description,
@JsonProperty("status") String status
) {
/**
* Convierte de forma segura el 'httpCode' a un código de estado HTTP numérico.
* @return El código de estado HTTP como un entero.
*/
public int getHttpStatusCode() {
try {
return Integer.parseInt(this.httpCode);
} catch (NumberFormatException e) {
return 500;
}
}
}

View File

@ -0,0 +1,11 @@
package domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public record FoundAccountInfo(
String clearAccount,
String username,
String otp,
String accountType
) {}

View File

@ -0,0 +1,15 @@
package domain.model;
import com.banesco.xmlns.enterpriseobjects.msgrshdr.MsgRsHdr;
import infrastructure.client.otp.dto.response.Status;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class Otp {
MsgRsHdr msgRsHdr;
Status status;
}

View File

@ -0,0 +1,12 @@
package domain.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class ServiceStatus {
String status;
}

View File

@ -0,0 +1,14 @@
package domain.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
@Builder
@RegisterForReflection
@JsonInclude(JsonInclude.Include.NON_NULL)
public record UserLogin(
String usernameBol,
String usernameBOLE,
String idNumber
) { }

View File

@ -0,0 +1,9 @@
package domain.repository;
import domain.entity.AppConfiguration;
import java.util.Optional;
public interface AppConfigRepository {
Optional<AppConfiguration> findConfigByAppName(String appName);
}

View File

@ -0,0 +1,10 @@
package domain.repository;
import application.port.input.command.CustomerAccountCommand;
import domain.model.CustomerAccount;
import java.util.List;
public interface ICustomerProductPort {
List<CustomerAccount> execute(CustomerAccountCommand command, String requestId);
}

View File

@ -0,0 +1,7 @@
package domain.repository;
import application.port.input.command.EncodeValueCommand;
public interface IEncodeValuePort {
String execute(EncodeValueCommand command, String requestId);
}

View File

@ -0,0 +1,8 @@
package domain.repository;
import application.port.input.command.ValidateOtpCommand;
import org.jboss.resteasy.reactive.RestResponse;
public interface IOtpPort {
RestResponse<Void> validateOtp(ValidateOtpCommand command, String requestId);
}

View File

@ -0,0 +1,7 @@
package domain.repository;
import application.port.input.command.RegisterSecurityCommand;
public interface IRegisterSecurityPort {
void writeTrace(RegisterSecurityCommand request, String requestId);
}

View File

@ -0,0 +1,8 @@
package domain.repository;
import application.port.input.command.ServiceStatusCommand;
import domain.model.ServiceStatus;
public interface IStatusPort {
ServiceStatus getServiceStatus(ServiceStatusCommand query, String requestId);
}

View File

@ -0,0 +1,11 @@
package domain.repository;
import application.port.input.command.UserLoginCommand;
import domain.model.UserLogin;
public interface IUserLoginPort {
UserLogin execute(
UserLoginCommand command,
String requestId
);
}

View File

@ -0,0 +1,8 @@
package domain.repository;
import application.port.input.command.ValidateProfileCommand;
import domain.model.ClientProfile;
public interface IValidateProfilePort {
ClientProfile execute(ValidateProfileCommand command, String requestId);
}

View File

@ -0,0 +1,111 @@
package infrastructure.adapter.input.rest;
import application.helper.ResponseHelper;
import application.port.input.IAppConfig;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.dto.response.CustomerResponse;
import domain.entity.AppConfiguration;
import infrastructure.context.TraceIdContext;
import infrastructure.service.StatusDescriptionResolver;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.jboss.resteasy.reactive.RestPath;
@Slf4j
@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class AppConfigResource {
// --- CASOS DE USO Y SERVICIOS ---
private final IAppConfig appConfigUseCase;
// --- DEPENDENCIAS ADICIONALES ---
private final ResponseHelper responseHelper;
private final TraceIdContext traceIdContext;
public AppConfigResource(
IAppConfig appConfigUseCase,
ResponseHelper responseHelper,
TraceIdContext traceIdContext,
StatusDescriptionResolver statusDescriptionResolver
) {
this.appConfigUseCase = appConfigUseCase;
this.responseHelper = responseHelper;
this.traceIdContext = traceIdContext;
}
@GET
@Path("/config/{appName}")
@Operation(
summary = "Obtener Configuración de Aplicación",
description = "Obtiene la configuración JSON completa para un 'appName' (appId) específico. Este endpoint se usa para testing o recarga manual."
)
@Parameter(
name = "appName",
in = ParameterIn.PATH,
description = "El 'appName' (appId) que se usará para buscar la configuración.",
required = true,
schema = @Schema(type = SchemaType.STRING),
example = "abc123"
)
@APIResponses(value = {
@APIResponse(
responseCode = "200",
description = "Configuración encontrada.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = AppConfiguration.class),
examples = {
@ExampleObject(
name = "Configuración encontrada",
externalValue = "/openapi-examples/response/app-config.json"
)
}
)
),
@APIResponse(
responseCode = "400",
description = "Configuración no encontrada. El 'appName' no existe en la base de datos.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = CustomerResponse.class),
examples = {
@ExampleObject(
name = "Configuración no encontrada",
summary = "VRN_CONFIG_NOT_FOUND",
externalValue = "/openapi-examples/response/config-not-found.json"
)
}
)
)
})
public Response getConfig(@RestPath String appName) {
final var requestId = traceIdContext.getTraceId();
try {
var config = appConfigUseCase.execute(appName);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada para appName: " + appName));
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion: " + config));
return Response.ok(config).build();
} catch (NotFoundException ex) {
log.warn(LoggerHelper.buildError(requestId, "Configuracion no encontrada (VRN_CONFIG_NOT_FOUND) para appName: " + appName));
return responseHelper.buildErrorResponse("VRN_CONFIG_NOT_FOUND", requestId);
}
}
}

View File

@ -0,0 +1,540 @@
package infrastructure.adapter.input.rest;
import application.factory.SecurityTraceFactory;
import application.helper.ResponseHelper;
import application.port.input.*;
import application.port.input.command.*;
import application.service.OtpValidationService;
import application.service.PostgresConfigService;
import com.banesco.common.infraestructure.helpers.JsonHelper;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.dto.request.CustomerRequest;
import domain.dto.response.CustomerResponse;
import domain.dto.response.StatusResponse;
import domain.entity.AppConfiguration;
import domain.entity.ConfigItem;
import domain.model.AccountClientContext;
import domain.model.BankService;
import domain.model.ErrorCode;
import domain.model.UserLogin;
import infrastructure.config.AvailabilityBolConfig;
import infrastructure.context.TraceIdContext;
import infrastructure.service.StatusDescriptionResolver;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameters;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import java.util.Map;
import java.util.Optional;
@Slf4j
@Path("/customer")
public class CustomerResource {
@ConfigProperty(name = "app.redis.expiration")
long cacheTtl;
// --- DEPENDENCIAS INYECTADAS ---
private final TraceIdContext traceIdContext;
private final ResponseHelper responseHelper;
private final SecurityTraceFactory securityTraceFactory;
private final StatusDescriptionResolver statusDescriptionResolver;
// --- CONFIGURACIONES ---
private final AvailabilityBolConfig bolConfig;
// --- CASOS DE USO Y SERVICIOS ---
private final IAppConfig appConfigUseCase;
private final IServiceStatus serviceStatusUseCase;
private final IUserLogin userLoginUseCase;
private final IValidateProfile validateProfileUseCase;
private final IEncodeValue encodeValueUseCase;
private final ICustomerProduct customerProductUseCase;
private final IRegisterSecurity registerSecurityUseCase;
private final OtpValidationService otpValidationService;
private final PostgresConfigService postgresConfigService;
public CustomerResource(
TraceIdContext traceIdContext,
ResponseHelper responseHelper,
SecurityTraceFactory securityTraceFactory,
AvailabilityBolConfig bolConfig,
StatusDescriptionResolver statusDescriptionResolver,
IAppConfig appConfigUseCase,
IServiceStatus serviceStatusUseCase,
IUserLogin userLoginUseCase,
IValidateProfile validateProfileUseCase,
IEncodeValue encodeValueUseCase,
ICustomerProduct customerProductUseCase,
IRegisterSecurity registerSecurityUseCase,
OtpValidationService otpValidationService,
PostgresConfigService postgresConfigService
) {
this.traceIdContext = traceIdContext;
this.responseHelper = responseHelper;
this.securityTraceFactory = securityTraceFactory;
this.bolConfig = bolConfig;
this.statusDescriptionResolver = statusDescriptionResolver;
this.appConfigUseCase = appConfigUseCase;
this.serviceStatusUseCase = serviceStatusUseCase;
this.userLoginUseCase = userLoginUseCase;
this.validateProfileUseCase = validateProfileUseCase;
this.encodeValueUseCase = encodeValueUseCase;
this.customerProductUseCase = customerProductUseCase;
this.registerSecurityUseCase = registerSecurityUseCase;
this.otpValidationService = otpValidationService;
this.postgresConfigService = postgresConfigService;
}
@POST
@Path("/position/evaluate")
@Operation(
summary = "Consulta de cuentas",
description = "Orquesta el flujo de validaciones (estatus de servicio, perfil, OTP, etc.) y, si son exitosas, consulta los productos del cliente."
)
@Parameters({
@Parameter(
name = "requestId",
in = ParameterIn.HEADER,
description = "Identificador de la solicitud para la trazabilidad. Si se omite, se genera uno automático.",
schema = @Schema(type = SchemaType.STRING),
example = "7WCnvznd0CPrBZ0jF4af"
),
@Parameter(
name = "appId",
in = ParameterIn.HEADER,
description = "Identificador de la aplicación cliente para cargar una configuración de header específica. Si se omite, se usa la configuración por defecto.",
schema = @Schema(type = SchemaType.STRING),
example = "abc123"
)
})
@RequestBody(
description = "Cuerpo de la solicitud con los datos necesarios para consultar las cuentas.",
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema =
@Schema(implementation = CustomerRequest.class),
examples = {
@ExampleObject(
name = "Solicitud Estándar",
summary = "Ejemplo de solicitud con éxito",
externalValue = "/openapi-examples/request/customer-product-success.json"
),
@ExampleObject(
name = "Solicitud con Token",
summary = "Ejemplo de solicitud con token (éxito)",
externalValue = "/openapi-examples/request/customer-product-token-success.json"
)
}
)
)
@APIResponses(value = {
@APIResponse(
responseCode = "200",
description = "Operación Exitosa. Devuelve la lista de productos del cliente.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = CustomerResponse.class),
examples = {
@ExampleObject(
name = "Cuentas Enmascaradas",
summary = "Respuesta exitosa (Enmascaramiento activo)",
description = "La configuración de la app indica que 'encryptionData' es true.",
externalValue = "/openapi-examples/response/customer-product-response.json"
),
@ExampleObject(
name = "Cuentas Reales",
summary = "Respuesta exitosa (Sin enmascaramiento)",
description = "La configuración de la app indica que 'encryptionData' es false.",
externalValue = "/openapi-examples/response/customer-product-response-unmasked.json"
),
@ExampleObject(
name = "Sin Resultados",
summary = "Respuesta exitosa (Cliente sin productos)",
description = "La consulta se completó, pero el cliente no tiene productos que coincidan.",
externalValue = "/openapi-examples/response/customer-product-without-results.json"
)
}
)
),
@APIResponse(
responseCode = "401",
description = "Error de Autorización o Perfil. El perfil del cliente no es válido o falló una validación de seguridad.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = StatusResponse.class),
examples = {
@ExampleObject(
name = "Perfil Inválido",
summary = "VRN01 - Perfil no válido",
externalValue = "/openapi-examples/response/invalid-profile.json"
)
}
)
),
@APIResponse(
responseCode = "400",
description = "Datos de entrada inválidos, como un token OTP incorrecto o una configuración de app no encontrada.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = StatusResponse.class),
examples = {
@ExampleObject(
name = "Token Inválido",
summary = "VRN07 - OTP Inválido",
externalValue = "/openapi-examples/response/invalid-token.json"
),
@ExampleObject(
name = "Configuración no encontrada",
summary = "VRN_CONFIG_NOT_FOUND",
externalValue = "/openapi-examples/response/config-not-found.json"
)
}
)
),
@APIResponse(
responseCode = "404",
description = "Recurso no encontrado. Puede ser un usuario BOL, un login vacío.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = CustomerResponse.class),
examples = {
@ExampleObject(
name = "Usuario no existe",
summary = "AUTH02 - Usuario no existe",
externalValue = "/openapi-examples/response/user-bol-not-exist.json"
),
@ExampleObject(
name = "Usuario vacío",
summary = "AUTH01 - Login vacío",
externalValue = "/openapi-examples/response/user-not-exist.json"
),
}
)
),
@APIResponse(
responseCode = "409",
description = "CONFLICTO.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = StatusResponse.class),
examples = {
@ExampleObject(
name = "Conflicto",
summary = "Conflicto (revisar)",
externalValue = "/openapi-examples/response/conflict.json"
)
}
)
),
@APIResponse(
responseCode = "500",
description = "Uso Interno. Ocurrio un error inesperado.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = StatusResponse.class),
examples = {
@ExampleObject(
name = "Uso Interno",
summary = "Uso Interno (revisar)",
externalValue = "/openapi-examples/response/internal-use.json"
)
}
)
),
@APIResponse(
responseCode = "503",
description = "Servicio no disponible. Ocurrió un error con una dependencia interna o el servicio está en mantenimiento.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = StatusResponse.class),
examples = {
@ExampleObject(
name = "Mantenimiento",
summary = "VRN04 - Servicio en mantenimiento",
externalValue = "/openapi-examples/response/service-unavailable.json"
),
@ExampleObject(
name = "Error de Dependencia",
summary = "Error de CI",
externalValue = "/openapi-examples/response/customer-product-rest-client-error.json"
)
}
)
)
})
public Response getCustomerProducts(
@HeaderParam("appId") String appId,
@HeaderParam("customerReferenceFintechId") String customerReferenceFintechId,
@HeaderParam("customerReferenceUser") String customerReferenceUser,
@Valid @NotNull(message = "VDE01") CustomerRequest request
) {
final var requestId = traceIdContext.getTraceId();
final long startExecution = System.currentTimeMillis();
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, JsonHelper.getJsonFromObject(request)));
var context = new AccountClientContext(startExecution, requestId, request,
appId, customerReferenceFintechId, customerReferenceUser
);
var appConfigurationOpt = getPgConfig(context, requestId);
if (appConfigurationOpt.isEmpty()) {
var error = statusDescriptionResolver.getCode("VRN_CONFIG_NOT_FOUND");
return responseHelper.buildErrorResponse(error, requestId);
}
var appConfiguration = appConfigurationOpt.get();
var validationRules = appConfiguration.getConfiguration().getValidationRules();
var validationErrorOpt = runValidationPipeline(context, validationRules, requestId);
Response finalResponse;
if (validationErrorOpt.isPresent()) {
context.setFinalError(validationErrorOpt);
finalResponse = responseHelper.buildErrorResponse(validationErrorOpt.get(), requestId);
} else {
var configItem = findSpecificConfigItem(appConfiguration, context, requestId);
if (configItem == null) {
log.error(LoggerHelper.buildError(requestId, "FATAL: No se encontro un ConfigItem (ni especifico ni 'all')"));
var error = statusDescriptionResolver.getCode("VRN_ITEM_NOT_FOUND");
return responseHelper.buildErrorResponse(error, requestId);
}
var otp = Optional.ofNullable(request.getTokenAssignmentInstance())
.map(CustomerRequest.TokenAssignmentInstance::getTokenIdentificationCode)
.filter(otpCode -> !otpCode.isEmpty())
.orElse(null);
finalResponse = getUserAccounts(context, appConfiguration, configItem, otp, requestId);
}
var fullResponse = (CustomerResponse) finalResponse.getEntity();
var statusRegister = fullResponse.getStatusResponse();
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Ejecutando tareas finales (traza de seguridad)"));
var securityCommand = securityTraceFactory.createCommandFromContext(context, statusRegister);
registerSecurityUseCase.execute(securityCommand, context.getRequestId());
return finalResponse;
}
/**
* Ejecuta la secuencia de validaciones. Si alguna falla, devuelve la respuesta de error.
* Si todas pasan, devuelve un Optional vacío.
*/
@SuppressWarnings("t")
private Optional<ErrorCode> runValidationPipeline(AccountClientContext context, Map<String, Boolean> validationRules, String requestId) {
var data = context.getRequest();
var device = data.getDevice();
var customerReference = data.getCustomerPositionState().getCustomerReference();
var tokenInstance = data.getTokenAssignmentInstance();
if (shouldRun(validationRules, "serviceStatus")) {
if (!isServiceActive(context.getRequestId())) {
return Optional.of(statusDescriptionResolver.getCode("VRN04"));
}
}
// Primero intenta obtener el usuario del contexto
var userOpt = Optional.ofNullable(context.getUsername())
.filter(u -> !u.isBlank())
.map(u -> UserLogin.builder()
.usernameBol(u)
.idNumber(customerReference.getCustomerId())
.build());
// Si no está en el contexto Y la regla dice que se debe ejecutar, búscalo en la API
if (shouldRun(validationRules, "getUserLogin")) {
userOpt = getUserLogin(context.getRequestId(), customerReference.getCustomerId(), customerReference.getCustomerIdType());
if (userOpt.isEmpty() ) {
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "No se pudo obtener el usuario ni desde el contexto ni desde la API"));
return Optional.of(statusDescriptionResolver.getCode("VRN19"));
}
// Si viene vacio el usernameBol ""
if(userOpt.get().usernameBol().isBlank()) {
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "No se pudo obtener el usernameBol"));
return Optional.of(statusDescriptionResolver.getCode("VRN20"));
}
context.setUsername(userOpt.get().usernameBol());
}
String hash = ""; // Inicializar
if (shouldRun(validationRules, "encodeValue")) {
hash = encodeUsername(context.getUsername(), requestId);
}
if (shouldRun(validationRules, "validateOtp")) {
if(!tokenInstance.getTokenIdentificationCode().isEmpty()) {
var isOtpValid = otpValidationService.isOtpValid(
tokenInstance.getTokenIdentificationCode(),
context.getFintechId(),
context.getUsername(),
device.getDeviceIp(),
requestId
);
if (!isOtpValid) {
return Optional.of(statusDescriptionResolver.getCode("VRN07"));
}
}
}
if (shouldRun(validationRules, "validateProfile")) {
var command = new ValidateProfileCommand(
device.getDeviceSessionReference() == null || device.getDeviceSessionReference().isBlank() ? context.getFinalCustomerId() + device.getDeviceSessionReference() : context.getFinalCustomerId(),
device.getDeviceIp(),
hash
);
var isValidProfile = validateProfileUseCase.execute(command, requestId);
if(!isValidProfile) {
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "FALLO LA VALIDACION DEL CLIENTE EN TRANSACT"));
return Optional.of(statusDescriptionResolver.getCode("VRN01"));
}
}
return Optional.empty();
}
// --- MÉTODOS PRIVADOS DE ORQUESTACIÓN ---
private boolean isServiceActive(String requestId) {
var bankService = new BankService(bolConfig.codBan(), bolConfig.codEve(), bolConfig.codServ());
var command = new ServiceStatusCommand(requestId, requestId, bankService);
return serviceStatusUseCase.execute(command, requestId);
}
private Optional<UserLogin> getUserLogin(String requestId, String... params) {
var command = new UserLoginCommand(
params[0],
params[1]
);
return userLoginUseCase.execute(command, requestId);
}
private String encodeUsername(String username, String requestId) {
var command = new EncodeValueCommand(username);
return encodeValueUseCase.execute(command, requestId);
}
private Optional<AppConfiguration> getPgConfig(AccountClientContext context, String requestId) {
var configKey = context.getAppId();
var cachedConfig = postgresConfigService.findByName(configKey, requestId);
if (cachedConfig.isPresent()) {
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion obtenida desde CACHE"));
return cachedConfig;
}
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion no encontrada en cache, buscando en BD..."));
AppConfiguration appConfiguration;
try {
appConfiguration = appConfigUseCase.execute(configKey);
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Configuracion obtenida desde BD"));
} catch (NotFoundException ex) {
log.error(LoggerHelper.buildError(requestId, "FATAL: No se encontro AppConfiguration para appId: " + configKey), ex);
return Optional.empty();
}
postgresConfigService.savePostgresConfig(configKey, appConfiguration, cacheTtl, requestId);
return Optional.of(appConfiguration);
}
/**
* Extrae el ConfigItem específico de la AppConfiguration basado en el contexto.
* Cae a "all" si no se encuentra uno específico.
*/
private ConfigItem findSpecificConfigItem(AppConfiguration appConfiguration, AccountClientContext context, String requestId) {
var data = context.getRequest().getCustomerPositionState().getCustomerReference();
var postgresConfig = appConfiguration.getConfiguration();
var aliases = postgresConfig.getAliases();
var configMap = postgresConfig.getConfiguration();
// Usamos el appId del contexto como prefijo de la clave
var literal = data.getCustomerIdType();
var finalLiteral = aliases.getOrDefault(literal, literal);
var specificKey = context.getAppId() + "_" + finalLiteral;
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Buscando ConfigItem con KEY: " + specificKey));
ConfigItem config;
if (configMap.containsKey(specificKey)) {
config = configMap.get(specificKey);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada (Especifica): " + specificKey));
} else {
config = configMap.get("all");
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada (General): all"));
}
return config; // Puede ser null si "all" tampoco existe
}
/**
* Helper para verificar de forma segura si una regla debe ejecutarse.
* Por defecto, si una regla no está en el mapa, se ejecuta (falla seguro).
*/
private boolean shouldRun(Map<String, Boolean> rules, String ruleName) {
if (rules == null) {
// Si no hay mapa, orquesta completo
return true;
}
return rules.getOrDefault(ruleName, true);
}
private Response getUserAccounts(AccountClientContext context, AppConfiguration appConfig, ConfigItem configItem, String otp, String requestId) {
var customerAccountCommand = CustomerAccountCommand.builder()
.rif(context.getFinalCustomerId())
.bankCode(configItem.getBankCode())
.currencyCode(configItem.getCurrencyCode())
.status(configItem.getStatus())
.statusMe(configItem.getStatusMe())
.productCv(configItem.getProductCv())
.product(configItem.getProduct())
.channel(configItem.getChannel())
.service(configItem.getService())
.operator(configItem.getOperator())
.phone(configItem.getPhone())
.affiliationStatus(configItem.getAffiliationStatus())
.counterpartyAccount(configItem.getCounterpartyAccount())
.holder(configItem.getHolder())
.build();
var products = customerProductUseCase.execute(customerAccountCommand, context, appConfig, otp, configItem.isCacheable(), requestId);
return responseHelper.buildSuccessResponse(
products.stream().toList(),
products.isEmpty() ?
statusDescriptionResolver.getCode("204") :
statusDescriptionResolver.getCode("200"),
requestId
);
}
}

View File

@ -0,0 +1,25 @@
package infrastructure.adapter.output;
import application.port.output.IMaintenanceCheckPort;
import com.banesco.common.infraestructure.utils.MaintenanceUtil;
import infrastructure.config.AccountAvailabilityConfig;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class MaintenanceCheckAdapter implements IMaintenanceCheckPort {
private final AccountAvailabilityConfig availabilityConfig;
@Inject
MaintenanceCheckAdapter(AccountAvailabilityConfig paymentAvailabilityConfig) {
this.availabilityConfig = paymentAvailabilityConfig;
}
@Override
public Boolean isUnderMaintenance(String requestId) {
return MaintenanceUtil.isDateInRange(requestId, availabilityConfig.begin(), availabilityConfig.finish());
}
}

View File

@ -0,0 +1,53 @@
package infrastructure.adapter.output.persistence;
import com.fasterxml.jackson.databind.ObjectMapper;
import domain.entity.AppConfiguration;
import domain.model.Configuration;
import domain.repository.AppConfigRepository;
import io.agroal.api.AgroalDataSource;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Optional;
@ApplicationScoped
public class AppConfigRepositoryAdapter implements AppConfigRepository {
@Inject
AgroalDataSource dataSource;
@Inject
ObjectMapper objectMapper;
@Override
public Optional<AppConfiguration> findConfigByAppName(String appName) {
String sql = "SELECT * FROM get_application_config(?)";
try (var conn = dataSource.getConnection();
var ps = conn.prepareStatement(sql)) {
ps.setString(1, appName);
try (var rs = ps.executeQuery()) {
if (rs.next()) {
var config = new AppConfiguration();
config.setAppId(rs.getLong("id_appconfig"));
config.setAppName(rs.getString("app_name"));
config.setApiName(rs.getString("api_name"));
config.setType(rs.getString("type"));
config.setEvent(rs.getString("event"));
config.setEncryptionData(rs.getBoolean("encryption_data"));
var jsonString = rs.getString("configuration");
if (jsonString != null) {
config.setConfiguration(objectMapper.readValue(jsonString, Configuration.class));
}
return Optional.of(config);
}
}
} catch (Exception e) {
throw new RuntimeException("Error al llamar a get_application_config", e);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,75 @@
package infrastructure.client.accounts.adapter;
import application.exception.GatewayServiceException;
import application.port.input.command.CustomerAccountCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.model.CustomerAccount;
import domain.repository.ICustomerProductPort;
import infrastructure.client.accounts.api.CustomerAccountClient;
import infrastructure.mapper.CustomerAccountMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.WebApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.List;
@Slf4j
@ApplicationScoped
public class CustomerProductAdapter implements ICustomerProductPort {
private final CustomerAccountClient client;
public CustomerProductAdapter(@RestClient CustomerAccountClient client) {
this.client = client;
}
@Override
public List<CustomerAccount> execute(CustomerAccountCommand command, String requestId) {
try {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para api-get-db2-accounts-client-v2"));
var request = CustomerAccountMapper.toRequestDto(command);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de api-get-db2-accounts-client-v2", request));
var response = client.readCustomerProducts(requestId, request);
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de api-get-db2-accounts-client-v2", response));
// Validación de contrato: asegura que la respuesta no sea nula o inválida.
if (response == null) {
log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de api-get-db2-accounts-client-v2"));
throw new GatewayServiceException("Respuesta invalida del servicio externo de api-get-db2-accounts-client-v2");
}
return CustomerAccountMapper.toEntityList(response);
} catch (WebApplicationException ex) {
var errorResponse = ex.getResponse();
var status = errorResponse.getStatus();
String errorDetails;
// Verifica si el Content-Type es HTML
if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP %s del servicio de api-get-db2-accounts-client-v2 %s", status, errorDetails)));
} else {
// Si no es HTML, probablemente es un error JSON que queremos ver.
var errorBody = errorResponse.readEntity(String.class);
// Aun así, es buena idea truncarlo por si es muy largo
errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP no manejado del servicio de api-get-db2-accounts-client-v2 Status: %s. Body: %s", status, errorDetails)));
}
throw new GatewayServiceException("Error en el servicio de api-get-db2-accounts-client-v2: " + errorDetails);
} catch (RuntimeException ex) {
// Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
log.error(LoggerHelper.buildError(
requestId,
String.format("Error de comunicacion con el servicio de api-get-db2-accounts-client-v2: %s", ex.getMessage())
));
// Traduce el error técnico a una excepción de la aplicación.
throw new GatewayServiceException("No se pudo comunicar con el servicio externo de api-get-db2-accounts-client-v2");
}
}
}

View File

@ -0,0 +1,21 @@
package infrastructure.client.accounts.api;
import infrastructure.client.accounts.dto.request.CustomerAccountRequest;
import infrastructure.client.accounts.dto.respone.CustomerAccountResponse;
import infrastructure.interceptor.LoggingFilter;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient(configKey = "customer-accounts")
@RegisterProvider(LoggingFilter.class)
public interface CustomerAccountClient {
@POST
@ClientHeaderParam(name = "appId", value = "${app.appId}")
CustomerAccountResponse readCustomerProducts(
@HeaderParam("requestId") String requestId,
CustomerAccountRequest request
);
}

View File

@ -0,0 +1,25 @@
package infrastructure.client.accounts.dto.request;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class CustomerAccountRequest {
String rif;
String bankCode;
String currencyCode;
String status;
String statusMe;
String productCv;
String product;
String channel;
String service;
String operator;
String phone;
String affiliationStatus;
String counterpartyAccount;
String holder;
}

View File

@ -0,0 +1,44 @@
package infrastructure.client.accounts.dto.respone;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
import java.math.BigDecimal;
import java.util.List;
@Value
@Builder
@RegisterForReflection
public class CustomerAccountResponse {
List<CustomerAccount> data;
int record;
@Value
@RegisterForReflection
public static class CustomerAccount {
String banco;
String cuenta;
String producto;
String descProd;
String claseCta;
String tipoCta;
String moneda;
BigDecimal saldoDisponible;
String estatus;
String statusMe;
String titular;
BigDecimal maximumPerDay;
BigDecimal maximumPerTransaction;
BigDecimal maximumPerMonth;
BigDecimal maximumMonthlyFee;
BigDecimal minimumPerTransaction;
String operator;
String phone;
String channel;
String service;
String affiliationStatus;
String codeError;
String descError;
}
}

View File

@ -0,0 +1,74 @@
package infrastructure.client.encodevalue.adapter;
import application.exception.GatewayServiceException;
import application.exception.ServiceCommunicationException;
import application.port.input.command.EncodeValueCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IEncodeValuePort;
import infrastructure.client.encodevalue.api.EncodeValueClient;
import infrastructure.mapper.EncodeValueMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.WebApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Slf4j
@ApplicationScoped
public class EncodeValueAdapter implements IEncodeValuePort {
private final EncodeValueClient client;
public EncodeValueAdapter(@RestClient EncodeValueClient client) {
this.client = client;
}
@Override
public String execute( EncodeValueCommand command, String requestId) {
try {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Encode Value"));
var requestDto = EncodeValueMapper.toRequestDto(command);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de encode value", requestDto));
var response = client.encodeValue(requestId, requestDto);
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de encode value", response));
// Validación de contrato: asegura que la respuesta no sea nula o inválida.
if (response == null) {
log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de encode value"));
throw new GatewayServiceException("Respuesta invalida del servicio externo de encode value");
}
return EncodeValueMapper.toResponseDto(response);
} catch (WebApplicationException ex) {
var errorResponse = ex.getResponse();
var status = errorResponse.getStatus();
String errorDetails;
// Verifica si el Content-Type es HTML
if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP %s del servicio de encode value. %s", status, errorDetails)));
} else {
// Si no es HTML, probablemente es un error JSON que queremos ver.
var errorBody = errorResponse.readEntity(String.class);
// Aun así, es buena idea truncarlo por si es muy largo
errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP no manejado del servicio de encode value. Status: %s. Body: %s", status, errorDetails)));
}
throw new GatewayServiceException("Error en el servicio de encode value: " + errorDetails);
} catch (RuntimeException ex) {
// Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
log.error(LoggerHelper.buildError(
requestId,
String.format("Error de comunicacion con el servicio de encode value: %s", ex)
));
throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de encode value", ex);
}
}
}

View File

@ -0,0 +1,21 @@
package infrastructure.client.encodevalue.api;
import infrastructure.interceptor.LoggingFilter;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.tempuri.Prueba;
import org.tempuri.PruebaResponse;
@RegisterRestClient(configKey = "encode-value")
@RegisterProvider(LoggingFilter.class)
public interface EncodeValueClient {
@POST
@ClientHeaderParam(name = "appId", value = "${app.appId}")
PruebaResponse encodeValue(
@HeaderParam("requestId") String requestId,
Prueba request
);
}

View File

@ -0,0 +1,91 @@
package infrastructure.client.otp.adapter;
import application.exception.GatewayServiceException;
import application.exception.InvalidTokenException;
import application.port.input.command.ValidateOtpCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IOtpPort;
import infrastructure.client.otp.api.OtpClient;
import infrastructure.mapper.OtpMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.WebApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.reactive.RestResponse;
@Slf4j
@ApplicationScoped
public class OtpAdapter implements IOtpPort {
private final OtpClient client;
@Inject
public OtpAdapter(@RestClient OtpClient client) {
this.client = client;
}
/**
* Implementa la comunicación con la API externa para validar un OTP.
* Se encarga de mapear el DTO, llamar al cliente REST y TRADUCIR errores
* técnicos (de red) o respuestas HTTP inesperadas en excepciones de la aplicación.
*
* @param command El comando de dominio con los datos de validación.
* @param requestId El ID de la solicitud para trazabilidad.
* @return Un objeto {@link RestResponse} que contiene la respuesta HTTP completa de la API.
* @throws GatewayServiceException si ocurre un error de red o la API responde con un error 5xx.
*/
@Override
public RestResponse<Void> validateOtp(ValidateOtpCommand command, String requestId) {
try {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para la API privada de validate otp"));
var requestDto = OtpMapper.toRequestDto(command);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de validate otp", requestDto));
var response = client.validateOtp(
requestId,
requestDto.getOtpToken(),
requestDto.getAppOrigen(),
requestDto.getLogin(),
requestDto.getIpAddress(),
requestDto.getCanal(),
requestDto.getServicio()
);
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de otp", response.getStatus()));
return response;
} catch(WebApplicationException ex) {
var errorResponse = ex.getResponse();
var status = errorResponse.getStatus();
String errorDetails;
// OTP inválido (respuesta de la privada)
if(status == 403) {
log.error(LoggerHelper.buildError(requestId, String.format("token %s invalido, respuesta HTTP API privada de otp: %s", command.otpToken(), status)));
throw new InvalidTokenException("Token inválido");
}
// Verifica si el Content-Type es HTML
if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP %s del servicio de otp. %s", status, errorDetails)));
} else {
// Si no es HTML, probablemente es un error JSON que queremos ver.
var errorBody = errorResponse.readEntity(String.class);
// Se trunca por si es el error es muy largo
errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP no manejado del servicio de otp. Status: %s. Body: %s", status, errorDetails)));
}
throw new GatewayServiceException("Error en el servicio de otp: " + errorDetails);
} catch(RuntimeException ex) {
// Error de red (Timeout, no se puede conectar, etc.)
log.error(LoggerHelper.buildError(requestId, String.format("Error de comunicacion con el servicio de otp %s", ex)));
throw new GatewayServiceException("No se pudo comunicar con el servicio externo de otp.");
}
}
}

View File

@ -0,0 +1,28 @@
package infrastructure.client.otp.api;
import infrastructure.interceptor.LoggingFilter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestResponse;
@RegisterRestClient(configKey = "otp")
@RegisterProvider(LoggingFilter.class)
public interface OtpClient {
@GET
@ClientHeaderParam(name = "appId", value = "${app.appId}")
@Path("/{otpToken}/{appOrigen}/{login}/{ipAddress}/{canal}/{servicio}")
RestResponse<Void> validateOtp(
@HeaderParam("requestId") String requestId,
@PathParam("otpToken") String otpToken,
@PathParam("appOrigen") String appOrigen,
@PathParam("login") String login,
@PathParam("ipAddress") String ipAddress,
@PathParam("canal") String canal,
@PathParam("servicio") String servicio
);
}

View File

@ -0,0 +1,19 @@
package infrastructure.client.otp.dto.request;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class ValidateOtpRequest {
// MsgRqHdr msgRqHdr;
String otpToken;
String appOrigen;
String login;
String ipAddress;
String canal;
String servicio;
// String domain;
}

View File

@ -0,0 +1,13 @@
package infrastructure.client.otp.dto.response;
import lombok.Value;
@Value
public class AditionalStatus {
String statusType;
String statusCode;
String statusDesc;
String validationType;
String severity;
String lineNumber;
}

View File

@ -0,0 +1,22 @@
package infrastructure.client.otp.dto.response;
import com.banesco.xmlns.enterpriseobjects.status.AdditionalStatus;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Value;
import java.math.BigInteger;
import java.util.List;
@Value
@RegisterForReflection
public class Status {
String statusType;
String statusCode;
String statusDesc;
String applicationName;
BigInteger lineNumber;
List<AdditionalStatus> additionalStatus;
String severity;
String statusInd;
String logId;
}

View File

@ -0,0 +1,14 @@
package infrastructure.client.otp.dto.response;
import com.banesco.xmlns.enterpriseobjects.msgrshdr.MsgRsHdr;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@RegisterForReflection
public class ValidateOtpResponse {
MsgRsHdr msgRsHdr;
Status status;
}

View File

@ -0,0 +1,89 @@
package infrastructure.client.registersecurity.adapter;
import application.port.input.command.RegisterSecurityCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.repository.IRegisterSecurityPort;
import infrastructure.client.registersecurity.api.RegisterSecurityClient;
import infrastructure.mapper.RegisterSecurityMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.WebApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Slf4j
@ApplicationScoped
public class RegisterSecurityAdapter implements IRegisterSecurityPort {
private final RegisterSecurityClient client;
private final RegisterSecurityMapper mapper;
@Inject
public RegisterSecurityAdapter(
@RestClient RegisterSecurityClient client,
RegisterSecurityMapper mapper
) {
this.client = client;
this.mapper = mapper;
}
@Override
public void writeTrace(RegisterSecurityCommand command, String requestId) {
try {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para register security"));
var requestDto = mapper.toRequestDto(command);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de register security (asincrono)", requestDto));
client.registerSecurity(requestId, requestDto)
.subscribe().with(
// 1. Callback de ÉXITO (sin cambios)
response -> log.info(LoggerHelper.buildInfoPrivateResponse(
requestId,
String.format("Traza de seguridad registrada. Status: %s", response.getStatus())
)),
// 2. Callback de FALLO (con la nueva lógica detallada)
failure -> handleFailure(failure, requestId)
);
} catch (Exception e) {
// Este bloque captura errores síncronos (ej. en el mapeo)
log.error(LoggerHelper.buildError(
requestId,
String.format("Error al preparar la llamada para registrar la traza de seguridad: %s", e.getMessage())
), e);
}
}
/**
* Centralizar y detallar el manejo de errores asíncronos.
*
* @param failure La excepción ocurrida.
* @param requestId El ID de la traza.
*/
private void handleFailure(Throwable failure, String requestId) {
if (failure instanceof WebApplicationException ex) {
// Lógica para errores HTTP (4xx, 5xx) - Equivalente al primer catch
var errorResponse = ex.getResponse();
var status = errorResponse.getStatus();
String errorDetails;
if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP %s del servicio de register security. %s", status, errorDetails)));
} else {
var errorBody = errorResponse.readEntity(String.class);
errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP no manejado del servicio de register security. Status: %s. Body: %s", status, errorDetails)));
}
} else {
// Lógica para otros errores (red, timeout, etc.) - Equivalente al segundo catch
log.error(LoggerHelper.buildError(
requestId,
String.format("Error de comunicacion con el servicio de register security: %s", failure.getMessage())
), failure);
}
}
}

View File

@ -0,0 +1,20 @@
package infrastructure.client.registersecurity.api;
import infrastructure.client.registersecurity.dto.request.RegisterSecurityRequest;
import infrastructure.interceptor.LoggingFilter;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient(configKey = "register-security")
@RegisterProvider(LoggingFilter.class)
public interface RegisterSecurityClient {
@POST
Uni<Response> registerSecurity(
@HeaderParam("requestId") String requestId,
RegisterSecurityRequest request
);
}

View File

@ -0,0 +1,39 @@
package infrastructure.client.registersecurity.dto.request;
import io.quarkus.runtime.annotations.RegisterForReflection;
import java.util.Date;
@RegisterForReflection
public record RegisterSecurityRequest (
String codBan,
String codMon,
String codEve,
String codEve2,
String login,
Date fecHor,
String nacCli,
Integer cedRifCli,
String tipoProductoCli,
String tipoProductoBen,
String productoCli,
String codEmpresa,
String nacBen,
Integer cedBen,
String nombreBen,
String productoBen,
Double monto,
String referencia,
String nroDePago,
String desPago,
String objeto,
Integer tipoRespuesta,
String msgRespuesta,
Integer tiempoRespuesta,
String codFintech,
String cedRifFintech,
String tipoDispositivo,
String desDispositivo,
String ipCli,
String sp
) {}

View File

@ -0,0 +1,86 @@
package infrastructure.client.servicestatus.adapter;
import application.exception.GatewayServiceException;
import application.exception.ServiceCommunicationException;
import application.port.input.command.ServiceStatusCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.model.ServiceStatus;
import domain.repository.IStatusPort;
import infrastructure.client.servicestatus.api.ServiceStatusClient;
import infrastructure.mapper.ServiceStatusMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.WebApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Slf4j
@ApplicationScoped
public class ServiceStatusAdapter implements IStatusPort {
private final ServiceStatusClient client;
@Inject
public ServiceStatusAdapter(@RestClient ServiceStatusClient client) {
this.client = client;
}
/**
* Implementa la comunicación con la API externa para obtener el estado de un servicio.
*
* @param command El comando de dominio con los datos de la consulta.
* @param requestId El ID de la solicitud para trazabilidad.
* @return Un objeto {@link ServiceStatus} con el estado del servicio.
* @throws GatewayServiceException si el servicio externo no es accesible, devuelve un error,
* o la respuesta tiene un formato inesperado.
*/
@Override
public ServiceStatus getServiceStatus(ServiceStatusCommand command, String requestId) {
try {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Service Status"));
var requestDto = ServiceStatusMapper.toRequestDto(command);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de service status", requestDto));
var response = client.getServiceStatus(requestId, requestDto);
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de service status", response));
// Validación de contrato: asegura que la respuesta no sea nula o inválida.
if (response == null || response.getStatus() == null) {
log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de service status."));
throw new GatewayServiceException("Respuesta invalida del servicio externo de estado.");
}
return ServiceStatusMapper.toEntity(response);
} catch (WebApplicationException ex) {
var errorResponse = ex.getResponse();
var status = errorResponse.getStatus();
String errorDetails;
// Verifica si el Content-Type es HTML
if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP %s del servicio de service status. %s", status, errorDetails)));
} else {
// Si no es HTML, probablemente es un error JSON que queremos ver.
var errorBody = errorResponse.readEntity(String.class);
// Aun así, es buena idea truncarlo por si es muy largo
errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP no manejado del servicio de service status. Status: %s. Body: %s", status, errorDetails)));
}
throw new GatewayServiceException("Error en el servicio de service status: " + errorDetails);
} catch (RuntimeException ex) {
// Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
log.error(LoggerHelper.buildError(
requestId,
String.format("Error de comunicacion con el servicio de estado: %s", ex.getMessage())
));
throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de service status", ex);
}
}
}

View File

@ -0,0 +1,21 @@
package infrastructure.client.servicestatus.api;
import com.banesco.common.infraestructure.web.dto.request.ServiceStatusRequest;
import com.banesco.common.infraestructure.web.dto.response.ServiceStatusResponse;
import infrastructure.interceptor.LoggingFilter;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient(configKey = "get-service-status")
@RegisterProvider(LoggingFilter.class)
public interface ServiceStatusClient {
@POST
@ClientHeaderParam(name = "appId", value = "${app.appId}")
ServiceStatusResponse getServiceStatus(
@HeaderParam("requestId") String requestId,
ServiceStatusRequest request
);
}

View File

@ -0,0 +1,75 @@
package infrastructure.client.userlogin.adapter;
import application.exception.GatewayServiceException;
import application.exception.ServiceCommunicationException;
import application.port.input.command.UserLoginCommand;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import domain.model.UserLogin;
import domain.repository.IUserLoginPort;
import infrastructure.client.userlogin.api.UserLoginClient;
import infrastructure.mapper.UserLoginMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.WebApplicationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Slf4j
@ApplicationScoped
public class UserLoginAdapter implements IUserLoginPort {
private final UserLoginClient client;
private final UserLoginMapper mapper;
public UserLoginAdapter(
@RestClient UserLoginClient client,
UserLoginMapper mapper
) {
this.client = client;
this.mapper = mapper;
}
@Override
public UserLogin execute(UserLoginCommand command, String requestId) {
try {
log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para la API privada de user login"));
var requestDto = mapper.toRequestDto(command, requestId);
log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de user login", requestDto));
var response = client.getUserLogin(requestId, requestDto);
log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de user login", response));
if (response == null) {
log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de user login."));
throw new GatewayServiceException("La respuesta del servicio de user login fue nula.");
}
return UserLoginMapper.toEntity(response);
} catch (WebApplicationException ex) {
var errorResponse = ex.getResponse();
var status = errorResponse.getStatus();
String errorDetails;
// Verifica si el Content-Type es HTML
if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP %s del servicio de user login. %s", status, errorDetails)));
} else {
// Si no es HTML, probablemente es un error JSON que queremos ver.
var errorBody = errorResponse.readEntity(String.class);
// Aun así, es buena idea truncarlo por si es muy largo
errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
log.error(LoggerHelper.buildError(requestId,
String.format("Error HTTP no manejado del servicio de user login. Status: %s. Body: %s", status, errorDetails)));
}
throw new GatewayServiceException("Error en el servicio de user login: " + errorDetails);
} catch (RuntimeException ex) {
// Error de red (Timeout, no se puede conectar, etc.)
log.error(LoggerHelper.buildError(requestId, String.format("Error de comunicacion con el servicio de user login %s", ex)));
throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de user login", ex);
}
}
}

Some files were not shown because too many files have changed in this diff Show More