commit 8a3c41b3db558ca7164374b74a6067b110d30d4a Author: Bishwajeet Kumar Rajak Date: Tue Jun 16 20:19:21 2026 +0530 first commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8dea6c2 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/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.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=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 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@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.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..78afe40 --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + + + com.main + ipks + 0.0.1-SNAPSHOT + ipks + Account Opening Via API + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + ${java.version} + ${java.version} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/main/ipks/IpksCbsApplication.java b/src/main/java/com/main/ipks/IpksCbsApplication.java new file mode 100644 index 0000000..a9fcdd9 --- /dev/null +++ b/src/main/java/com/main/ipks/IpksCbsApplication.java @@ -0,0 +1,13 @@ +package com.main.ipks; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class IpksCbsApplication { + + public static void main(String[] args) { + SpringApplication.run(IpksCbsApplication.class, args); + } + +} diff --git a/src/main/java/com/main/ipks/api/GlobalExceptionHandler.java b/src/main/java/com/main/ipks/api/GlobalExceptionHandler.java new file mode 100644 index 0000000..ab4c6b2 --- /dev/null +++ b/src/main/java/com/main/ipks/api/GlobalExceptionHandler.java @@ -0,0 +1,73 @@ +package com.main.ipks.api; + +import com.main.ipks.service.errors.*; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** Token generation/unavailability -> 503 */ + @ExceptionHandler(TokenFetchException.class) + public ResponseEntity> handleToken(TokenFetchException ex, HttpServletRequest req) { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .contentType(MediaType.APPLICATION_JSON) + .body(Map.of( + "status", "FAIL", + "message", ex.getMessage() != null ? ex.getMessage() : "Token service unavailable", + "response", null, + "error", 1 + )); + } + + /** Downstream returned HTTP 200 but body.status == FAIL -> return the same JSON as-is to client with 400 (or as you set). */ + @ExceptionHandler(DownstreamBusinessPayloadException.class) + public ResponseEntity handleBusinessPayload(DownstreamBusinessPayloadException ex) { + HttpStatus status = ex.getStatusToReturn() != null ? ex.getStatusToReturn() : HttpStatus.BAD_REQUEST; + return ResponseEntity.status(status) + .contentType(MediaType.APPLICATION_JSON) + .body(ex.getRawJson()); // raw JSON from C-EDGE, unchanged + } + + /** Empty/blank body from downstream -> 502 with standard FAIL JSON */ + @ExceptionHandler(DownstreamEmptyBodyException.class) + public ResponseEntity> handleEmptyBody(DownstreamEmptyBodyException ex) { + return ResponseEntity.status(HttpStatus.BAD_GATEWAY) + .contentType(MediaType.APPLICATION_JSON) + .body(Map.of( + "status", "FAIL", + "message", ex.getMessage() != null ? ex.getMessage() : "Empty response from downstream", + "response", null, + "error", 1 + )); + } + + /** Optional: transport/format/HTTP errors -> 502 (can also propagate raw body if you like) */ + @ExceptionHandler(DownstreamBadResponseException.class) + public ResponseEntity> handleBadGateway(DownstreamBadResponseException ex) { + return ResponseEntity.status(HttpStatus.BAD_GATEWAY) + .contentType(MediaType.APPLICATION_JSON) + .body(Map.of( + "status", "FAIL", + "message", ex.getMessage() != null ? ex.getMessage() : "Downstream call failed", + "response", null, + "error", 1 + )); + } + + /** Fallback */ + @ExceptionHandler(Exception.class) + public ResponseEntity> handleAny(Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(Map.of( + "status", "FAIL", + "message", "Unexpected error", + "response", null, + "error", 1 + )); + } +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/config/RestTemplateConfig.java b/src/main/java/com/main/ipks/config/RestTemplateConfig.java new file mode 100644 index 0000000..1d56bbf --- /dev/null +++ b/src/main/java/com/main/ipks/config/RestTemplateConfig.java @@ -0,0 +1,42 @@ +package com.main.ipks.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import javax.net.ssl.*; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + try { + // Trust all certificates (TEMP ONLY) + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); + + SimpleClientHttpRequestFactory factory = + new SimpleClientHttpRequestFactory(); + + return new RestTemplate(factory); + + } catch (Exception e) { + throw new RuntimeException("Failed to create SSL ignoring RestTemplate", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/controller/IpksController.java b/src/main/java/com/main/ipks/controller/IpksController.java new file mode 100644 index 0000000..016851b --- /dev/null +++ b/src/main/java/com/main/ipks/controller/IpksController.java @@ -0,0 +1,63 @@ +package com.main.ipks.controller; + +import com.main.ipks.dto.cif.*; +import com.main.ipks.dto.account.*; +import jakarta.validation.Valid; +import org.springframework.http.MediaType; + +import org.springframework.web.bind.annotation.RestController; +import com.main.ipks.service.CbsIntegrationService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + + + + + + +@RestController +@RequestMapping("/ipks") +public class IpksController { + + private static final Logger log = LoggerFactory.getLogger(IpksController.class); + + private final CbsIntegrationService cbsIntegrationService; + + public IpksController(CbsIntegrationService cbsIntegrationService) { + this.cbsIntegrationService = cbsIntegrationService; + } + + + @PostMapping( + value = "/customer", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE + ) + public ResponseEntity createCustomer(@Valid @RequestBody IpksCustomerRequest request) { + log.info("IPKS CIF creation request received..."); + CifApiResponse response = cbsIntegrationService.createCustomer(request); + + return ResponseEntity.ok(response); + } + + + @PostMapping( + value = "/account", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE + ) + public ResponseEntity createAccount(@Valid @RequestBody IpksAccountRequest request) { + log.info("IPKS Account creation request (CIF={})", request.getCif()); + AccountCreationResponse resp = cbsIntegrationService.createAccount(request); + + return ResponseEntity.ok(resp); + } + + + +} diff --git a/src/main/java/com/main/ipks/dto/TokenResponse.java b/src/main/java/com/main/ipks/dto/TokenResponse.java new file mode 100644 index 0000000..a5742e2 --- /dev/null +++ b/src/main/java/com/main/ipks/dto/TokenResponse.java @@ -0,0 +1,66 @@ +package com.main.ipks.dto; + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + + +@JsonIgnoreProperties(ignoreUnknown = true) // ignore any extra fields from server +public class TokenResponse { + + @JsonProperty("status") + private String status; + + @JsonProperty("message") + private String message; + + + @JsonProperty("response") + private String response; + + @JsonProperty("error") + private int error; + + + public TokenResponse() {} + + + public TokenResponse(String status, String message, String response, int error) { + this.status = status; + this.message = message; + this.response = response; + this.error = error; + } + + // --- Getters & Setters --- + + public String getStatus() { + return status; + } + public void setStatus(String status) { + this.status = status; + } + + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + + public String getResponse() { + return response; + } + public void setResponse(String response) { + this.response = response; + } + + public int getError() { + return error; + } + public void setError(int error) { + this.error = error; + } + + +} diff --git a/src/main/java/com/main/ipks/dto/account/AccountCreationResponse.java b/src/main/java/com/main/ipks/dto/account/AccountCreationResponse.java new file mode 100644 index 0000000..1c8cf2e --- /dev/null +++ b/src/main/java/com/main/ipks/dto/account/AccountCreationResponse.java @@ -0,0 +1,45 @@ + +package com.main.ipks.dto.account; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AccountCreationResponse { + + @JsonProperty("status") + private String status; + + @JsonProperty("message") + private String message; + + @JsonProperty("response") + private ResponseData response; + + @JsonProperty("error") + private int error; + + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public ResponseData getResponse() { return response; } + public void setResponse(ResponseData response) { this.response = response; } + + public int getError() { return error; } + public void setError(int error) { this.error = error; } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ResponseData { + + @JsonProperty("RcptData") + private String rcptData; + + public String getRcptData() { return rcptData; } + public void setRcptData(String rcptData) { this.rcptData = rcptData; } + + } + +} diff --git a/src/main/java/com/main/ipks/dto/account/CreateAccountRequest.java b/src/main/java/com/main/ipks/dto/account/CreateAccountRequest.java new file mode 100644 index 0000000..f80dbd4 --- /dev/null +++ b/src/main/java/com/main/ipks/dto/account/CreateAccountRequest.java @@ -0,0 +1,63 @@ + +package com.main.ipks.dto.account; +import com.fasterxml.jackson.annotation.JsonProperty; + + +public class CreateAccountRequest { + + @JsonProperty("AcctNum1") + private Long acctNum1; + + @JsonProperty("DfltStr1") + private String dfltStr1 = ""; + + @JsonProperty("DfltStr2") + private String dfltStr2 = ""; + + @JsonProperty("DfltStr3") + private String dfltStr3 = ""; + + @JsonProperty("DfltStr4") + private String dfltStr4 = ""; + + // --- Getters & Setters --- + public Long getAcctNum1() { + return acctNum1; + } + + public void setAcctNum1(Long acctNum1) { + this.acctNum1 = acctNum1; + } + + public String getDfltStr1() { + return dfltStr1; + } + + public void setDfltStr1(String dfltStr1) { + this.dfltStr1 = dfltStr1; + } + + public String getDfltStr2() { + return dfltStr2; + } + + public void setDfltStr2(String dfltStr2) { + this.dfltStr2 = dfltStr2; + } + + public String getDfltStr3() { + return dfltStr3; + } + + public void setDfltStr3(String dfltStr3) { + this.dfltStr3 = dfltStr3; + } + + public String getDfltStr4() { + return dfltStr4; + } + + public void setDfltStr4(String dfltStr4) { + this.dfltStr4 = dfltStr4; + } +} diff --git a/src/main/java/com/main/ipks/dto/account/CreateAccountResponse.java b/src/main/java/com/main/ipks/dto/account/CreateAccountResponse.java new file mode 100644 index 0000000..d5c234b --- /dev/null +++ b/src/main/java/com/main/ipks/dto/account/CreateAccountResponse.java @@ -0,0 +1,33 @@ + +package com.main.ipks.dto.account; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CreateAccountResponse { + + @JsonProperty("status") + private String status; + + @JsonProperty("message") + private String message; + + @JsonProperty("response") + private Map response; + + @JsonProperty("error") + private int error; + + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Map getResponse() { return response; } + public void setResponse(Map response) { this.response = response; } + public int getError() { return error; } + public void setError(int error) { this.error = error; } + +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/dto/account/CreateDepositAccountOkRequest.java b/src/main/java/com/main/ipks/dto/account/CreateDepositAccountOkRequest.java new file mode 100644 index 0000000..85d479a --- /dev/null +++ b/src/main/java/com/main/ipks/dto/account/CreateDepositAccountOkRequest.java @@ -0,0 +1,174 @@ +package com.main.ipks.dto.account; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + + +@JsonInclude(JsonInclude.Include.ALWAYS) +public class CreateDepositAccountOkRequest { + + // ---- Six dynamic fields (set in service) ---- + @JsonProperty("AcctNum4") private long acctNum4; + @JsonProperty("DfltStr1") private String dfltStr1 = ""; + @JsonProperty("DfltStr2") private String dfltStr2 = ""; + @JsonProperty("DfltStr3") private String dfltStr3 = ""; + @JsonProperty("DfltStr4") private String dfltStr4 = ""; + @JsonProperty("DfltStr8") private String dfltStr8 = ""; + + // ---- Fixed defaults ---- + @JsonProperty("AcctNum1") private int acctNum1 = 0; + @JsonProperty("DfltStr26") private String dfltStr26 = "N"; + @JsonProperty("HomeBrch1") private int homeBrch1 = 0; + @JsonProperty("ATMRq1") private String atmRq1 = ""; + @JsonProperty("Name11") private String name11 = ""; + @JsonProperty("ATMCard1") private String atmCard1 = "DOM"; + @JsonProperty("NomReqd1") private String nomReqd1 = "N"; + @JsonProperty("DfltStr6") private String dfltStr6 = "IN"; + @JsonProperty("NomiCIF1") private String nomiCIF1 = ""; + @JsonProperty("AgentCode") private String agentCode = ""; + @JsonProperty("Receipttype1") private int receipttype1 = 0; + + @JsonProperty("DefInt21") private int defInt21 = 10; + @JsonProperty("DefInt22") private int defInt22 = 11; + @JsonProperty("DefInt23") private int defInt23 = 1; + @JsonProperty("DefInt24") private int defInt24 = 10; + @JsonProperty("DefInt25") private int defInt25 = 0; + @JsonProperty("Intpcode1") private String intpcode1 = "R"; + + @JsonProperty("AcctNum2") private int acctNum2 = 0; + @JsonProperty("DefInt9") private int defInt9 = 0; + @JsonProperty("Languagecode1") private int languagecode1 = 1; + @JsonProperty("DfltStr17") private String dfltStr17 = ""; + @JsonProperty("DfltStr22") private String dfltStr22 = ""; + @JsonProperty("DefInt1") private int defInt1 = 0; + @JsonProperty("Mailind1") private String mailind1 = "1"; + @JsonProperty("Noteind1") private String noteind1 = "0"; + @JsonProperty("AcctNum3") private int acctNum3 = 0; + + @JsonProperty("Unsigned41") private String unsigned41 = "0.0"; + @JsonProperty("Yesno3") private String yesno3 = "N"; + @JsonProperty("Yesno2") private String yesno2 = "N"; + @JsonProperty("FreqN1") private String freqN1 = ""; + @JsonProperty("DefInt4") private int defInt4 = 0; + + @JsonProperty("DefInt5") private int defInt5 = 0; + @JsonProperty("DefInt10") private int defInt10 = 0; + @JsonProperty("DefInt11") private int defInt11 = 0; + @JsonProperty("DefInt12") private int defInt12 = 0; + @JsonProperty("DfltStr49") private String dfltStr49 = ""; + + @JsonProperty("DefInt2") private int defInt2 = 0; + @JsonProperty("AcctNum5") private int acctNum5 = 0; + @JsonProperty("DfltStr12") private String dfltStr12 = ""; + @JsonProperty("Acctmgmtgrp") private String acctmgmtgrp = ""; + @JsonProperty("DfltStr19") private String dfltStr19 = ""; + + @JsonProperty("DfltStr20") private String dfltStr20 = ""; + @JsonProperty("DefInt7") private int defInt7 = 0; + @JsonProperty("DefInt13") private int defInt13 = 0; + @JsonProperty("DfltStr21") private String dfltStr21 = ""; + @JsonProperty("Amt2") private String amt2 = "0.0"; + + @JsonProperty("DfltStr15") private String dfltStr15 = ""; + @JsonProperty("DfltStr10") private String dfltStr10 = ""; + @JsonProperty("DfltStr11") private String dfltStr11 = "ZZ"; + @JsonProperty("DfltStr28") private String dfltStr28 = "ZZ"; + @JsonProperty("DfltStr27") private String dfltStr27 = "Z"; + + @JsonProperty("DfltStr25") private String dfltStr25 = "ZZ"; + @JsonProperty("DfltStr18") private String dfltStr18 = "ZZ"; + @JsonProperty("DfltStr9") private String dfltStr9 = ""; + @JsonProperty("Dt1") private int dt1 = 0; + @JsonProperty("DefInt6") private int defInt6 = 0; + + @JsonProperty("Notipros1") private String notipros1 = "0"; + @JsonProperty("DfltStr14") private String dfltStr14 = ""; + @JsonProperty("Signed42") private String signed42 = "0.0"; + @JsonProperty("DefInt3") private int defInt3 = 0; + @JsonProperty("DfltStr16") private String dfltStr16 = ""; + + @JsonProperty("DfltStr13") private String dfltStr13 = ""; + @JsonProperty("DfltStr23") private String dfltStr23 = ""; + @JsonProperty("BrkrNum1") private int brkrNum1 = 0; + @JsonProperty("DefInt8") private int defInt8 = 0; + @JsonProperty("Signed41") private String signed41 = "0.0"; + + @JsonProperty("Unsigned1") private int unsigned1 = 706; + @JsonProperty("Yesno6") private String yesno6 = "N"; + @JsonProperty("DefInt28") private int defInt28 = 0; + @JsonProperty("RMPB") private String rmpb = ""; + @JsonProperty("AgentCode1") private String agentCode1 = ""; + + @JsonProperty("VendorCode") private int vendorCode = 0; + @JsonProperty("DefInt16") private int defInt16 = 0; + @JsonProperty("DefInt17") private int defInt17 = 0; + @JsonProperty("DefInt18") private int defInt18 = 0; + @JsonProperty("Dt2") private int dt2 = 0; + + @JsonProperty("Yesno1") private String yesno1 = "N"; + @JsonProperty("Dacctype2") private int dacctype2 = 0; + @JsonProperty("DefInt19") private int defInt19 = 0; + @JsonProperty("DfltStr31") private String dfltStr31 = ""; + @JsonProperty("FreqN2") private String freqN2 = ""; + + @JsonProperty("Amt1") private String amt1 = "0.0"; + @JsonProperty("DfltStr32") private String dfltStr32 = "0"; + @JsonProperty("DefInt20") private int defInt20 = 0; + @JsonProperty("DfltStr33") private String dfltStr33 = "N"; + @JsonProperty("DfltStr34") private String dfltStr34 = ""; + + @JsonProperty("DfltStr35") private String dfltStr35 = "L"; + @JsonProperty("DefInt27") private int defInt27 = 0; + @JsonProperty("DdoCode") private int ddoCode = 0; + @JsonProperty("DfltStr37") private String dfltStr37 = ""; + @JsonProperty("DfltStr38") private String dfltStr38 = ""; + + @JsonProperty("DfltStr39") private String dfltStr39 = ""; + @JsonProperty("DfltStr40") private String dfltStr40 = ""; + @JsonProperty("DfltStr41") private String dfltStr41 = ""; + @JsonProperty("PrimAcctNum") private int primAcctNum = 0; + @JsonProperty("Dintcat2") private int dintcat2 = 0; + + @JsonProperty("DfltStr42") private String dfltStr42 = ""; + @JsonProperty("DefInt26") private int defInt26 = 0; + @JsonProperty("DfltStr43") private String dfltStr43 = ""; + @JsonProperty("DfltStr44") private String dfltStr44 = ""; + @JsonProperty("DfltStr45") private String dfltStr45 = ""; + + @JsonProperty("DfltStr46") private String dfltStr46 = ""; + @JsonProperty("DfltStr47") private String dfltStr47 = ""; + @JsonProperty("Instlmnt") private int instlmnt = 0; + @JsonProperty("PrdBal") private String prdBal = "0.0"; + @JsonProperty("IrregularCnt") private int irregularCnt = 0; + + @JsonProperty("ExtnCnt") private int extnCnt = 0; + @JsonProperty("AcctStat") private int acctStat = 0; + @JsonProperty("PrimAcct") private String primAcct = ""; + @JsonProperty("AdvcReqd") private String advcReqd = "N"; + @JsonProperty("TypeofGold") private String typeofGold = ""; + + @JsonProperty("DfltStr48") private String dfltStr48 = ""; + @JsonProperty("MortgageOffsetInd") private String mortgageOffsetInd = ""; + @JsonProperty("NomiFlag") private String nomiFlag = ""; + + // --- Getters/Setters for 6 dynamic fields (others optional) --- + public long getAcctNum4() { return acctNum4; } + public void setAcctNum4(long acctNum4) { this.acctNum4 = acctNum4; } + + public String getDfltStr1() { return dfltStr1; } + public void setDfltStr1(String dfltStr1) { this.dfltStr1 = dfltStr1; } + + public String getDfltStr2() { return dfltStr2; } + public void setDfltStr2(String dfltStr2) { this.dfltStr2 = dfltStr2; } + + public String getDfltStr3() { return dfltStr3; } + public void setDfltStr3(String dfltStr3) { this.dfltStr3 = dfltStr3; } + + public String getDfltStr4() { return dfltStr4; } + public void setDfltStr4(String dfltStr4) { this.dfltStr4 = dfltStr4; } + + public String getDfltStr8() { return dfltStr8; } + public void setDfltStr8(String dfltStr8) { this.dfltStr8 = dfltStr8; } + +} diff --git a/src/main/java/com/main/ipks/dto/account/IpksAccountRequest.java b/src/main/java/com/main/ipks/dto/account/IpksAccountRequest.java new file mode 100644 index 0000000..3ae9899 --- /dev/null +++ b/src/main/java/com/main/ipks/dto/account/IpksAccountRequest.java @@ -0,0 +1,33 @@ + +package com.main.ipks.dto.account; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class IpksAccountRequest { + + public IpksAccountRequest(){} + + @NotBlank(message = "cif must not be blank") + @JsonProperty("CIF") + @JsonAlias({"cif","custNum","cust_num"}) + private String cif; + + + + // getters/setters + public String getCif() { return cif; } + public void setCif(String cif) { this.cif = cif; } + + + +} + + + + diff --git a/src/main/java/com/main/ipks/dto/cif/CifApiResponse.java b/src/main/java/com/main/ipks/dto/cif/CifApiResponse.java new file mode 100644 index 0000000..bff9576 --- /dev/null +++ b/src/main/java/com/main/ipks/dto/cif/CifApiResponse.java @@ -0,0 +1,45 @@ +package com.main.ipks.dto.cif; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +public class CifApiResponse { + + @JsonProperty("status") + private String status; + + @JsonProperty("message") + private String message; + + @JsonProperty("response") + private ResponseData response; // nullable in FAIL case + + @JsonProperty("error") + private int error; + + public String getStatus() { return status; } + public String getMessage() { return message; } + public ResponseData getResponse() { return response; } + public int getError() { return error; } + + public void setStatus(String status) { this.status = status; } + public void setMessage(String message) { this.message = message; } + public void setResponse(ResponseData response) { this.response = response; } + public void setError(int error) { this.error = error; } + + // Nested object for "response" + public static class ResponseData { + + @JsonProperty("RcptData") + private String rcptData; + + @JsonProperty("CustNum") + private String custNum; + + public String getRcptData() { return rcptData; } + public String getCustNum() { return custNum; } + + public void setRcptData(String rcptData) { this.rcptData = rcptData; } + public void setCustNum(String custNum) { this.custNum = custNum; } + } +} diff --git a/src/main/java/com/main/ipks/dto/cif/CifCreateRequest.java b/src/main/java/com/main/ipks/dto/cif/CifCreateRequest.java new file mode 100644 index 0000000..0af0915 --- /dev/null +++ b/src/main/java/com/main/ipks/dto/cif/CifCreateRequest.java @@ -0,0 +1,500 @@ + +package com.main.ipks.dto.cif; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + + +@JsonInclude(JsonInclude.Include.ALWAYS) // include nulls where CBS expects them (e.g., Exflag3/Exflag4) +public class CifCreateRequest { + + // ---------- Mandatory / dynamic ---------- + + @JsonProperty("FirstName") + private String firstName; + + @JsonProperty("LastName") + private String lastName; + + @JsonProperty("Addr1") + private String addr1; + + @JsonProperty("Addr2") + private String addr2; + + @JsonProperty("Addr3") + private String addr3; + + @JsonProperty("CityCode") + private String cityCode; + + @JsonProperty("PstCode") + private String pstCode; + + @JsonProperty("CustType") + private String custType; + + @JsonProperty("Dob") + private String dob; + + @JsonProperty("Gndr") + private String gndr; + + @JsonProperty("KYCDate") + private String kycDate; + + @JsonProperty("MaritalStat") + private String maritalStat; + + @JsonProperty("MotherMdnName") + private String motherMdnName; + + @JsonProperty("IdentityTyp") + private String identityTyp; + + @JsonProperty("IdNum") + private String idNum; + + @JsonProperty("KycStatus") + private int kycStatus; + + @JsonProperty("IdTyp2") + private String idTyp2; + + @JsonProperty("Addr4") + private String addr4; + + @JsonProperty("CustMidName") + private String custMidName; + + @JsonProperty("Title") + private String title ; + + @JsonProperty("Natlty") + private String natlty; + + + @JsonProperty("TelexNum") + private String telexNum; + + @JsonProperty("ResdncyStat") + private String resdncyStat; + + @JsonProperty("CycInd") + private String cycInd; + + @JsonProperty("FatherOrSpouseName") + private String fatherOrSpouseName; + + + + + + + + + // ---------- Optional (defaulted permanently) ---------- + + + + + + @JsonProperty("CountryCode1") + private String countryCode1 = "IN"; + + @JsonProperty("Ocpncode1") + private String ocpncode1 = "0100"; + + + + + + @JsonProperty("DmstcRisk") + private String dmstcRisk = "ZZ"; + + @JsonProperty("XbrdrRisk") + private String xbrdrRisk = "ZZ"; + + + @JsonProperty("IdtypeHidden") + private String idtypeHidden = ""; + + // @JsonProperty("CycInd") + // private String cycInd = "M"; + + + + @JsonProperty("OrigCode") + private String origCode = "41"; + + @JsonProperty("BsrOrigCode") + private String bsrOrigCode = "41"; + + @JsonProperty("Incomerange") + private String incomerange = "01"; + + @JsonProperty("Trflag") + private String trflag = ""; + + @JsonProperty("Exemptionprofile") + private int exemptionprofile = 0; + + @JsonProperty("TradeEmailRepflag") + private String tradeEmailRepflag = ""; + + @JsonProperty("CustNum") + private long custNum = 0; + + @JsonProperty("CustTyp") + private String custTyp = "00"; + + @JsonProperty("RelnshpMgr") + private long relnshpMgr = 0; + + @JsonProperty("BussCustName") + private String bussCustName = ""; + + @JsonProperty("ThrsldLmt") + private long thrsldLmt = 0; + + @JsonProperty("HomePhnNum") + private String homePhnNum = ""; + + @JsonProperty("FaxNum") + private String faxNum = ""; + + @JsonProperty("BussPhnNum") + private String bussPhnNum = ""; + + @JsonProperty("Occup") + private String occup = ""; + + @JsonProperty("Lang") + private String lang = "01"; + + @JsonProperty("IdIssueDt") + private String idIssueDt = "00000000"; + + @JsonProperty("IdIssuePlace") + private String idIssuePlace = ""; + + @JsonProperty("VipCode") + private int vipCode = 0; + + @JsonProperty("Stat") + private int stat = 0; + + @JsonProperty("SegCode") + private String segCode = "0706"; + + @JsonProperty("LckrHldr") + private String lckrHldr = ""; + + @JsonProperty("TfnInd") + private int tfnInd = 0; + + @JsonProperty("HomeBrch") + private int homeBrch = 0; + + @JsonProperty("Cust") + private String cust = ""; + + @JsonProperty("CustParent") + private String custParent = ""; + + @JsonProperty("Indust") + private String indust = ""; + + @JsonProperty("Country") + private String country = ""; + + @JsonProperty("Sec") + private String sec = ""; + + @JsonProperty("IndustClassification") + private String industClassification = "01"; + + @JsonProperty("CountryRisk") + private String countryRisk = "IN"; + + @JsonProperty("GrpCode") + private String grpCode = ""; + + @JsonProperty("BussSecCode") + private String bussSecCode = ""; + + + + @JsonProperty("EstDt") + private String estDt = "00000000"; + + @JsonProperty("StrtDt") + private String strtDt = "00000000"; + + @JsonProperty("CustGrp") + private String custGrp = "0102"; + + @JsonProperty("DfltStr27") + private String dfltStr27 = ""; + + @JsonProperty("VisaIssueDt") + private String visaIssueDt = "00000000"; + + @JsonProperty("VisaExpryDt") + private String visaExpryDt = "00000000"; + + @JsonProperty("Detail") + private String detail = ""; + + @JsonProperty("LiquidDt") + private String liquidDt = "00000000"; + + @JsonProperty("PremDt") + private String premDt = "00000000"; + + @JsonProperty("NPATrade") + private String npaTrade = ""; + + @JsonProperty("NPANonTrade") + private String npaNonTrade = ""; + + @JsonProperty("TradeCustNum") + private String tradeCustNum = "N"; + + @JsonProperty("RefNum") + private long refNum = 0; + + @JsonProperty("ChanCode") + private int chanCode = 0; + + @JsonProperty("CountryCode") + private String countryCode = "IN"; + + @JsonProperty("StateCode") + private String stateCode = "32"; + + @JsonProperty("VoterIdNum") + private String voterIdNum = ""; + + @JsonProperty("AppTDS") + private String appTDS = "Y"; + + @JsonProperty("Rmrks") + private String rmrks = ""; + + @JsonProperty("RevaltnFlag") + private String revaltnFlag = "N"; + + @JsonProperty("GreetingReq") + private String greetingReq = ""; + + @JsonProperty("AcctNum2") + private long acctNum2 = 0; + + @JsonProperty("ChkFlag4") + private String chkFlag4 = "N"; + + @JsonProperty("Addr5") + private String addr5 = ""; + + @JsonProperty("EmailAddr") + private String emailAddr = ""; + + @JsonProperty("Internetkit") + private String internetkit = ""; + + @JsonProperty("FundMgmtUnit") + private int fundMgmtUnit = 0; + + @JsonProperty("SrcOtherExpns") + private String srcOtherExpns = ""; + + @JsonProperty("CustRisk") + private String custRisk = "00"; + + @JsonProperty("DiffAble") + private String diffAble = ""; + + @JsonProperty("Exflag1") + private String exflag1 = ""; + + @JsonProperty("Exflag2") + private String exflag2 = ""; + + @JsonProperty("Exflag3") + private String exflag3 = null; + + @JsonProperty("Exflag4") + private String exflag4 = null; + + @JsonProperty("Exflag5") + private String exflag5 = ""; + + @JsonProperty("Exfield1") + private String exfield1 = ""; + + @JsonProperty("Exfield2") + private String exfield2 = ""; + + @JsonProperty("Exfield3") + private String exfield3 = ""; + + @JsonProperty("Exfield4") + private String exfield4 = ""; + + @JsonProperty("Exfield5") + private String exfield5 = ""; + + @JsonProperty("Exfield6") + private String exfield6 = ""; + + @JsonProperty("Exfield7") + private String exfield7 = ""; + + @JsonProperty("Exfield8") + private String exfield8 = ""; + + @JsonProperty("Exfield9") + private String exfield9 = ""; + + @JsonProperty("Exfield10") + private String exfield10 = ""; + + @JsonProperty("TradeGoldcif") + private String tradeGoldcif = ""; + + @JsonProperty("Tinvat") + private String tinvat = ""; + + @JsonProperty("Tan") + private String tan = ""; + + @JsonProperty("ServiceTaxRegno") + private String serviceTaxRegno = ""; + + @JsonProperty("DfltStr45") + private String dfltStr45 = ""; + + @JsonProperty("Npoflag") + private String npoflag = ""; + + + @JsonProperty("VillageCode") + private String villageCode = "Sector"; + + // @JsonProperty("FatherOrSpouseName") + // private String fatherOrSpouseName = ""; + + + // ---------- Getters & Setters ---------- + + public String getFirstName() { return firstName; } + + public void setFirstName(String firstName) { this.firstName = firstName; } + + public String getLastName() { return lastName; } + + public void setLastName(String lastName) { this.lastName = lastName; } + + public String getAddr1() { return addr1; } + + public void setAddr1(String addr1) { this.addr1 = addr1; } + + public String getAddr2() { return addr2; } + + public void setAddr2(String addr2) { this.addr2 = addr2; } + + public String getAddr3() { return addr3; } + + public void setAddr3(String addr3) { this.addr3 = addr3; } + + public String getCityCode() { return cityCode; } + + public void setCityCode(String cityCode) { this.cityCode = cityCode; } + + public String getPstCode() { return pstCode; } + + public void setPstCode(String pstCode) { this.pstCode = pstCode; } + + public String getCustType() { return custType; } + + public void setCustType(String custType) { this.custType = custType; } + + public String getDob() { return dob; } + + public void setDob(String dob) { this.dob = dob; } + + public String getGndr() { return gndr; } + + public void setGndr(String gndr) { this.gndr = gndr; } + + public String getKycDate() { return kycDate; } + + public void setKycDate(String kycDate) { this.kycDate = kycDate; } + + public String getMaritalStat() { return maritalStat; } + + public void setMaritalStat(String maritalStat) { this.maritalStat = maritalStat; } + + public String getMotherMdnName() { return motherMdnName; } + + public void setMotherMdnName(String motherMdnName) { this.motherMdnName = motherMdnName; } + + public String getIdentityTyp() { return identityTyp; } + + public void setIdentityTyp(String identityTyp) { this.identityTyp = identityTyp; } + + public String getIdNum() { return idNum; } + + public void setIdNum(String idNum) { this.idNum = idNum; } + + public int getKycStatus() { return kycStatus; } + + public void setKycStatus(int kycStatus) { this.kycStatus = kycStatus; } + + public String getIdTyp2() { return idTyp2; } + + public void setIdTyp2(String idTyp2) { this.idTyp2 = idTyp2; } + + public String getAddr4() { return addr4; } + + public void setAddr4(String addr4) { this.addr4 = addr4; } + + + + public String getCustMidName() { return custMidName; } + public void setCustMidName(String custMidName) { this.custMidName = custMidName; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getNatlty() { return natlty; } + public void setNatlty(String natlty) { this.natlty = natlty; } + + public String getTelexNum() { return telexNum; } + public void setTelexNum(String telexNum) { this.telexNum = telexNum; } + + public String getResdncyStat() { return resdncyStat; } + public void setResdncyStat(String resdncyStat) { this.resdncyStat = resdncyStat; } + + + + public String getCycInd() { return cycInd; } + public void setCycInd(String cycInd) { this.cycInd = cycInd; } + + public String getFatherOrSpouseName() { return fatherOrSpouseName; } + public void setFatherOrSpouseName(String fatherOrSpouseName) { this.fatherOrSpouseName = fatherOrSpouseName; } + + + +} + + + + + + + + + + diff --git a/src/main/java/com/main/ipks/dto/cif/IpksCustomerRequest.java b/src/main/java/com/main/ipks/dto/cif/IpksCustomerRequest.java new file mode 100644 index 0000000..06d10ce --- /dev/null +++ b/src/main/java/com/main/ipks/dto/cif/IpksCustomerRequest.java @@ -0,0 +1,306 @@ +package com.main.ipks.dto.cif; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + + +@JsonIgnoreProperties(ignoreUnknown = true) +public class IpksCustomerRequest { + + public IpksCustomerRequest() {} + + @NotBlank(message = "firstName must not be blank") + @JsonProperty("FIRST_NAME") + @JsonAlias({"firstName","first_name"}) + private String firstName; + + + @JsonProperty("LAST_NAME") + @JsonAlias({"lastName","last_name"}) + private String lastName; + + @NotBlank(message = "addr1 must not be blank") + @JsonProperty("ADD_1") + @JsonAlias({"addr1","ADDR1","add_1"}) + private String addr1; + + @JsonProperty("ADD_2") + @JsonAlias({"addr2","ADDR2","add_2"}) + private String addr2; + + + @JsonProperty("ADD_3") + @JsonAlias({"addr3","ADDR3","add_3"}) + private String addr3; + + @NotBlank(message = "cityCode must not be blank") + @JsonProperty("CITY_CODE") + @JsonAlias({"cityCode","city_code"}) + private String cityCode; + + @NotBlank(message = "pstCode/PO_NUMBER must not be blank") + @JsonProperty("PO_NUMBER") + @JsonAlias({"poNumber","po_number","pstCode","postCode"}) + private String pstCode; + + @NotBlank(message = "custType must not be blank") + @JsonProperty("CUSTOMER_TYPE") + @JsonAlias({"custType","customerType","customer_type"}) + private String custType; + + @NotBlank(message = "dob/BIRTH_DATE must not be blank") + @JsonProperty("BIRTH_DATE") + @JsonAlias({"dob","birthDate","birth_date"}) + @Pattern(regexp = "\\d{8}", message = "dob must be in ddmmyyyy") + private String dob; + + @NotBlank(message = "gender must not be blank") + @JsonProperty("GENDER") + @JsonAlias({"gndr","gender"}) + private String gndr; + + @NotBlank(message = "kycDate/SEND_DATE must not be blank") + @JsonProperty("SEND_DATE") + @JsonAlias({"kycDate","sendDate","send_date"}) + @Pattern(regexp = "\\d{8}", message = "kycDate must be in ddmmyyyy") + private String kycDate; + + @JsonProperty("STATUS") + @JsonAlias({"maritalStat","marital_status"}) + private String maritalStat; + + + @JsonProperty("MOTHER_NAME") + @JsonAlias({"motherMdnName","motherName","mother_name"}) + private String motherMdnName; + + @NotNull(message = "identityTyp/ID_TYPE must not be null") + @JsonProperty("ID_TYPE") + @JsonAlias({"identityTyp","idType","id_type"}) + private String identityTyp; + + @NotBlank(message = "idNum/ID_NUMBER must not be blank") + @JsonProperty("ID_NUMBER") + @JsonAlias({"idNum","idNumber","id_number"}) + private String idNum; + + @JsonProperty("KYC_STATUS") + @JsonAlias({"kycStatus","kyc_status"}) + private Integer kycStatus; + + @JsonProperty("ID_TYPE_TWO") + @JsonAlias({"idTyp2","idType2","id_type_two"}) + private String idTyp2; + + @JsonProperty("ADD4") + @JsonAlias({"ADD_4","add4","idNumberTwo"}) + private String addr4; + + + @JsonProperty("TITLE") + @JsonAlias({"title"}) + private String title; + + @JsonProperty("CustMidName") + @JsonAlias({"middleName","middle_Name"}) + private String custMidName; + + + @NotBlank(message = "natlty/Natlty must not be blank") + @JsonProperty("Natlty") + @JsonAlias({"nationality_Code","nationalityCode"}) + private String natlty; + + @NotBlank(message = "telexNum/TelexNum must not be blank") + @JsonProperty("TelexNum") + @JsonAlias({"telexNum","phNo","ph_No"}) + private String telexNum; + + @NotBlank(message = "resdncyStat/ResdncyStat must not be blank") + @JsonProperty("ResdncyStat") + @JsonAlias({"residency_status","residencyStatus"}) + private String resdncyStat; + + @JsonProperty("Relative_code") + @JsonAlias({"relative_code"}) + private String relativeCode; + + @JsonProperty("Relative_name") + @JsonAlias({"relative_name"}) + private String relativeName; + + + + + + + + // ===== Getters & Setters (camelCase) ===== + + public String getFirstName() { + return firstName; + } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getAddr1() { + return addr1; + } + public void setAddr1(String addr1) { + this.addr1 = addr1; + } + + public String getAddr2() { + return addr2; + } + public void setAddr2(String addr2) { + this.addr2 = addr2; + } + + public String getAddr3() { + return addr3; + } + public void setAddr3(String addr3) { + this.addr3 = addr3; + } + + public String getCityCode() { + return cityCode; + } + public void setCityCode(String cityCode) { + this.cityCode = cityCode; + } + + public String getPstCode() { + return pstCode; + } + public void setPstCode(String pstCode) { + this.pstCode = pstCode; + } + + public String getCustType() { + return custType; + } + public void setCustType(String custType) { + this.custType = custType; + } + + public String getDob() { + return dob; + } + public void setDob(String dob) { + this.dob = dob; + } + + public String getGndr() { + return gndr; + } + public void setGndr(String gndr) { + this.gndr = gndr; + } + + public String getKycDate() { + return kycDate; + } + public void setKycDate(String kycDate) { + this.kycDate = kycDate; + } + + public String getMaritalStat() { + return maritalStat; + } + public void setMaritalStat(String maritalStat) { + this.maritalStat = maritalStat; + } + + public String getMotherMdnName() { + return motherMdnName; + } + public void setMotherMdnName(String motherMdnName) { + this.motherMdnName = motherMdnName; + } + + public String getIdentityTyp() { + return identityTyp; + } + public void setIdentityTyp(String identityTyp) { + this.identityTyp = identityTyp; + } + + public String getIdNum() { + return idNum; + } + public void setIdNum(String idNum) { + this.idNum = idNum; + } + + public Integer getKycStatus() { + return kycStatus; + } + public void setKycStatus(Integer kycStatus) { + this.kycStatus = kycStatus; + } + + public String getIdTyp2() { + return idTyp2; + } + public void setIdTyp2(String idTyp2) { + this.idTyp2 = idTyp2; + } + + public String getAddr4() { + return addr4; + } + public void setAddr4(String addr4) { + this.addr4 = addr4; + } + + public String getCustMidName() { return custMidName; } + public void setCustMidName(String custMidName) { this.custMidName = custMidName; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getNatlty() { return natlty; } + public void setNatlty(String natlty) { this.natlty = natlty; } + + public String getTelexNum() { return telexNum; } + public void setTelexNum(String telexNum) { this.telexNum = telexNum; } + + public String getResdncyStat() { return resdncyStat; } + public void setResdncyStat(String resdncyStat) { this.resdncyStat = resdncyStat; } + + + public String getRelativeCode() { return relativeCode; } + public void setRelativeCode(String relativeCode) { this.relativeCode = relativeCode; } + + + public String getRelativeName() { return relativeName; } + public void setRelativeName(String relativeName) { this.relativeName = relativeName; } + +} + + + + + + + + + + + + + diff --git a/src/main/java/com/main/ipks/service/CbsIntegrationService.java b/src/main/java/com/main/ipks/service/CbsIntegrationService.java new file mode 100644 index 0000000..e6614a7 --- /dev/null +++ b/src/main/java/com/main/ipks/service/CbsIntegrationService.java @@ -0,0 +1,309 @@ +package com.main.ipks.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.main.ipks.dto.account.*; +import com.main.ipks.dto.cif.CifApiResponse; +import com.main.ipks.dto.cif.CifCreateRequest; +import com.main.ipks.dto.cif.IpksCustomerRequest; +import com.main.ipks.service.errors.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +@Service +public class CbsIntegrationService { + + private static final Logger log = LoggerFactory.getLogger(CbsIntegrationService.class); + + private final RestTemplate restTemplate; + private final TokenService tokenService; + private final ObjectMapper om = new ObjectMapper(); + + public CbsIntegrationService(RestTemplate restTemplate, TokenService tokenService) { + this.restTemplate = restTemplate; + this.tokenService = tokenService; + } + + @Value("${cedge.cif.url}") + private String cifUrl; + + @Value("${cedge.account.url}") + private String accountUrl; + + @Value("${cedge.accountOk.url}") + private String accountOkUrl; + + // ---------------- CIF Creation ---------------- + public CifApiResponse createCustomer(IpksCustomerRequest in) { + try { + // Token + headers (TokenFetchException handled globally -> 503) + String token = tokenService.getToken(); + HttpHeaders headers = buildHeaders(token); + + // Map inbound to C-EDGE payload (null-safe defaults) + CifCreateRequest req = mapToCbs(in); + log.debug("[C-EDGE] CIF-CREATION Request JSON: {}", safeJson(req)); + + HttpEntity entity = new HttpEntity<>(req, headers); + + ResponseEntity response; + try { + response = restTemplate.exchange(cifUrl, HttpMethod.POST, entity, String.class); + } catch (HttpStatusCodeException ex) { + if (ex.getStatusCode() == HttpStatus.UNAUTHORIZED || ex.getStatusCode() == HttpStatus.FORBIDDEN) { + log.warn("[C-EDGE] CIF call unauthorized. Refreshing token and retrying once..."); + HttpEntity retryEntity = + new HttpEntity<>(req, buildHeaders(tokenService.refreshToken())); + response = restTemplate.exchange(cifUrl, HttpMethod.POST, retryEntity, String.class); + } else { + // If upstream provided a JSON body, propagate it to client (as 502) + String raw = ex.getResponseBodyAsString(); + if (raw != null && !raw.isBlank() && looksJson(raw)) { + throw new DownstreamBusinessPayloadException(raw, HttpStatus.BAD_GATEWAY); + } + throw ex; + } + } + + String bodyStr = response.getBody(); + if (bodyStr == null || bodyStr.isBlank()) { + throw new DownstreamEmptyBodyException("Empty response from C-EDGE (CIF)"); + } + + // status/message to decide the path + JsonNode node = om.readTree(bodyStr); + String status = node.path("status").asText(null); + + if ("SUCCESS".equalsIgnoreCase(status)) { + // Success -> map to DTO and return + CifApiResponse body = om.readValue(bodyStr, CifApiResponse.class); + if (body.getResponse() != null) { + log.info("CIF Created..."); + } + return body; + } else { + // Business FAIL from C-EDGE -> propagate raw JSON as-is to client with 400 + log.info("[C-EDGE] CIF Creation Failed (business). Returning upstream payload as-is. {}",bodyStr); + throw new DownstreamBusinessPayloadException(bodyStr, HttpStatus.BAD_REQUEST); + } + + } catch (HttpStatusCodeException ex) { + // Non-401/403 HTTP errors without propagatable JSON + throw new DownstreamBadResponseException( + "C-EDGE CIF call failed: HTTP " + ex.getStatusCode().value(), ex); + } catch (RestClientException e) { + throw new DownstreamBadResponseException("C-EDGE CIF Creation failed: " + e.getMessage(), e); + } catch (DownstreamBusinessPayloadException | DownstreamEmptyBodyException e) { + throw e; // already mapped + } catch (Exception e) { + // JSON parsing/mapping + throw new DownstreamBadResponseException("CIF response processing error: " + e.getMessage(), e); + } + } + + // ---------------- Account Creation (API-1 + API-2) ---------------- + public AccountCreationResponse createAccount(IpksAccountRequest reqDto) { + try { + String token = tokenService.getToken(); + HttpHeaders headers = buildHeaders(token); + + // --- API-1 --- + CreateAccountRequest api1Req = new CreateAccountRequest(); + api1Req.setAcctNum1(Long.parseLong(reqDto.getCif())); + HttpEntity api1Entity = new HttpEntity<>(api1Req, headers); + + log.debug("[C-EDGE] API-1 for Account Creation: Request JSON: {}", safeJson(api1Req)); + + ResponseEntity api1Raw; + + try { + api1Raw = restTemplate.exchange(accountUrl, HttpMethod.POST, api1Entity, String.class); + } catch (HttpStatusCodeException ex) { + if (ex.getStatusCode() == HttpStatus.UNAUTHORIZED || ex.getStatusCode() == HttpStatus.FORBIDDEN) { + log.warn("[C-EDGE] API-1 unauthorized. Refresh + retry once"); + HttpEntity retryEntity = + new HttpEntity<>(api1Req, buildHeaders(tokenService.refreshToken())); + api1Raw = restTemplate.exchange(accountUrl, HttpMethod.POST, retryEntity, String.class); + } else { + String raw = ex.getResponseBodyAsString(); + if (raw != null && !raw.isBlank() && looksJson(raw)) { + throw new DownstreamBusinessPayloadException(raw, HttpStatus.BAD_GATEWAY); + } + throw ex; + } + } + + String api1Str = api1Raw.getBody(); + if (api1Str == null || api1Str.isBlank()) { + throw new DownstreamEmptyBodyException("Empty response from C-EDGE API-1 for Account Creation"); + } + + JsonNode api1Node = om.readTree(api1Str); + String api1Status = api1Node.path("status").asText(null); + + if (!"SUCCESS".equalsIgnoreCase(api1Status)) { + // Propagate upstream business FAIL as-is with 400 + log.error("[C-EDGE] API-1 business failure. Payload returned to client: {}", api1Str); + throw new DownstreamBusinessPayloadException(api1Str, HttpStatus.BAD_REQUEST); + } + + // Map to DTO to get response map + CreateAccountResponse api1Dto = om.readValue(api1Str, CreateAccountResponse.class); + Map m = api1Dto.getResponse(); + if (m == null) { + throw new DownstreamEmptyBodyException("C-EDGE API-1 for Account Creation response map is null"); + } + + // Build API-2 request + CreateDepositAccountOkRequest api2Req = new CreateDepositAccountOkRequest(); + String rawAcctNum4 = s(m.get("AcctNum4")); + String trimmedAcctNum4 = removeFirstFiveLeadingZeros(rawAcctNum4); + long acctNum4 = toLong(trimmedAcctNum4, 0L); + api2Req.setAcctNum4(acctNum4); + + api2Req.setDfltStr1(s(m.get("DfltStr1"))); + api2Req.setDfltStr2(s(m.get("DfltStr2"))); + api2Req.setDfltStr3(s(m.get("DfltStr3"))); + api2Req.setDfltStr4(s(m.get("DfltStr4"))); + api2Req.setDfltStr8(s(m.get("DfltStr8"))); + + log.debug("[C-EDGE] API-2 for Account Creation: Request JSON: {}", safeJson(api2Req)); + + // --- API-2 --- + HttpEntity api2Entity = new HttpEntity<>(api2Req, headers); + ResponseEntity api2Raw; + try { + api2Raw = restTemplate.exchange(accountOkUrl, HttpMethod.POST, api2Entity, String.class); + } catch (HttpStatusCodeException ex) { + if (ex.getStatusCode() == HttpStatus.UNAUTHORIZED || ex.getStatusCode() == HttpStatus.FORBIDDEN) { + log.warn("[C-EDGE] API-2 unauthorized. Refresh + retry once"); + HttpEntity retryEntity = + new HttpEntity<>(api2Req, buildHeaders(tokenService.refreshToken())); + api2Raw = restTemplate.exchange(accountOkUrl, HttpMethod.POST, retryEntity, String.class); + } else { + String raw = ex.getResponseBodyAsString(); + if (raw != null && !raw.isBlank() && looksJson(raw)) { + throw new DownstreamBusinessPayloadException(raw, HttpStatus.BAD_GATEWAY); + } + throw ex; + } + } + + String api2Str = api2Raw.getBody(); + if (api2Str == null || api2Str.isBlank()) { + throw new DownstreamEmptyBodyException("Empty response from C-EDGE API-2 for Account Creation"); + } + + JsonNode api2Node = om.readTree(api2Str); + String api2Status = api2Node.path("status").asText(null); + + if (!"SUCCESS".equalsIgnoreCase(api2Status)) { + // Propagate upstream business FAIL as-is with 400 + log.error("[C-EDGE] API-2 business failure. Payload returned to client: {}", api2Str); + throw new DownstreamBusinessPayloadException(api2Str, HttpStatus.BAD_REQUEST); + } + + // Success -> map final + AccountCreationResponse finalResp = om.readValue(api2Str, AccountCreationResponse.class); + log.info("Account Created..."); + + return finalResp; + + } catch (HttpStatusCodeException ex) { + throw new DownstreamBadResponseException( + "C-EDGE Account call failed: HTTP " + ex.getStatusCode().value(), ex); + } catch (RestClientException e) { + throw new DownstreamBadResponseException("C-EDGE Account Creation failed: " + e.getMessage(), e); + } catch (DownstreamBusinessPayloadException | DownstreamEmptyBodyException e) { + throw e; + } catch (Exception e) { + throw new DownstreamBadResponseException("Account response processing error: " + e.getMessage(), e); + } + } + + // ------------ Mapping (IpksCustomerRequest -> CifCreateRequest) ------------ + private CifCreateRequest mapToCbs(IpksCustomerRequest in) { + CifCreateRequest req = new CifCreateRequest(); + + req.setFirstName(def(in.getFirstName(), "")); + req.setLastName(def(in.getLastName(), "")); + req.setCustMidName(def(in.getCustMidName(), "")); + req.setTitle(def(in.getTitle(), "01")); + req.setMotherMdnName(def(in.getMotherMdnName(), "")); + req.setAddr1(def(in.getAddr1(), "")); + req.setAddr2(def(in.getAddr2(), "")); + req.setAddr3(def(in.getAddr3(), "")); + req.setCityCode(def(in.getCityCode(), "000")); + req.setPstCode(def(in.getPstCode(), "000000")); + req.setCustType(def(in.getCustType(), "")); + req.setIdentityTyp(def(in.getIdentityTyp(), "")); + req.setIdNum(def(in.getIdNum(), "")); + req.setDob(def(in.getDob(), "00000000")); + req.setGndr(def(in.getGndr(), "")); + req.setMaritalStat(def(in.getMaritalStat(), "U")); + req.setKycDate(def(in.getKycDate(), "00000000")); + req.setKycStatus(def(in.getKycStatus(), 0)); + req.setIdTyp2(def(in.getIdTyp2(), "")); + req.setAddr4(def(in.getAddr4(), "")); + req.setTelexNum(def(in.getTelexNum(), "0000000000")); + req.setResdncyStat(def(in.getResdncyStat(), "1")); + req.setNatlty(def(in.getNatlty(), "IN")); + req.setCycInd(def(in.getRelativeCode(),"")); + req.setFatherOrSpouseName(def(in.getRelativeName(),"")); + return req; + } + + // ------------Helpers--------------- + + private static String def(String v, String d) { + return (v == null || v.isBlank()) ? d : v.trim(); + } + + private static int def(Integer v, int d) { + return v == null ? d : v; + } + + private HttpHeaders buildHeaders(String token) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(token); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + return headers; + } + + private static String s(Object o) { return (o == null) ? "" : o.toString(); } + + private static String removeFirstFiveLeadingZeros(String s) { + if (s == null) return ""; + if (s.startsWith("00000")) return s.substring(5); + return s; + } + + private static long toLong(String s, long def) { + if (s == null || s.isBlank()) return def; + try { return Long.parseLong(s); } catch (Exception e) { return def; } + } + + private String safeJson(Object o) { + try { + return new ObjectMapper().writeValueAsString(o); + } catch (com.fasterxml.jackson.core.JsonProcessingException e) { + return ""; + } + } + + private static boolean looksJson(String s) { + String t = s.trim(); + return (t.startsWith("{") && t.endsWith("}")) || (t.startsWith("[") && t.endsWith("]")); + } + +} diff --git a/src/main/java/com/main/ipks/service/TokenService.java b/src/main/java/com/main/ipks/service/TokenService.java new file mode 100644 index 0000000..f8b1eda --- /dev/null +++ b/src/main/java/com/main/ipks/service/TokenService.java @@ -0,0 +1,128 @@ +package com.main.ipks.service; + +import com.main.ipks.dto.TokenResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import com.main.ipks.service.errors.TokenFetchException; + + +@Service +public class TokenService { + + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + + private final RestTemplate restTemplate; + + @Value("${cedge.token.url}") + private String tokenUrl; + + private volatile String cachedToken; + private volatile long tokenExpiryTimeMillis; + private final Object tokenLock = new Object(); + + private static final long DEFAULT_TOKEN_TTL_MS = 7L * 60 * 60 * 1000; + + public TokenService(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + public String getToken() { + long now = System.currentTimeMillis(); + if (cachedToken != null && now < tokenExpiryTimeMillis) { + return cachedToken; + } + + synchronized (tokenLock) { + now = System.currentTimeMillis(); + if (cachedToken != null && now < tokenExpiryTimeMillis) { + return cachedToken; + } + + // Only 2 attempts total + for (int attempt = 1; attempt <= 2; attempt++) { + try { + log.info("Fetching new C-EDGE token (attempt {}/2)", attempt); + + ResponseEntity resp = + restTemplate.getForEntity(tokenUrl, TokenResponse.class); + + TokenResponse body = resp.getBody(); + + if (body == null) { + throw new TokenFetchException("Token API returned empty body"); + } + + + String status = body.getStatus(); // "SUCCESS" or "FAIL" + String message = body.getMessage(); // reason + String token = body.getResponse(); // JWT when SUCCESS + int error = body.getError(); // 0 or 1 + + if (!"SUCCESS".equalsIgnoreCase(status)) { + // Upstream business failure → fail with upstream message + String msg = (message != null && !message.isBlank()) + ? message : "Token API returned FAIL"; + throw new TokenFetchException(msg); + } + + if (error != 0) { + // Defensive: If error is non-zero even with SUCCESS (shouldn't happen) + throw new TokenFetchException("Token API error: " + + (message != null ? message : "unknown")); + } + + if (token == null || token.isBlank()) { + throw new TokenFetchException("Token string missing in response"); + } + + cachedToken = token; + tokenExpiryTimeMillis = System.currentTimeMillis() + DEFAULT_TOKEN_TTL_MS; + + String preview = cachedToken.substring(0, Math.min(12, cachedToken.length())) + "..."; + log.info("Token fetched successfully. Preview={}", preview); + return cachedToken; + + } catch (RuntimeException e) { + // RestClientException also comes here because it extends RuntimeException + log.warn("Token fetch failed on attempt {}/2: {}", attempt, e.getMessage()); + + if (attempt == 1) { + try { + Thread.sleep(300); // small delay before retry + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new TokenFetchException("Interrupted during token retry wait", ie); + } + continue; + } + + // second attempt failed -> hard fail + log.error("Token generation failed after 2 attempts", e); + if (e instanceof TokenFetchException) { + throw e; + } + throw new TokenFetchException("Token generation failed", e); + } + } + + // should never reach here + throw new TokenFetchException("Token generation failed"); + } + } + + /** + * Force refresh token: clear cache and fetch again (with 2 attempts total). + * Used when C-EDGE returns 401/403 unauthorized. + */ + public String refreshToken() { + synchronized (tokenLock) { + cachedToken = null; + tokenExpiryTimeMillis = 0L; + } + return getToken(); + } +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/service/errors/DownstreamBadResponseException.java b/src/main/java/com/main/ipks/service/errors/DownstreamBadResponseException.java new file mode 100644 index 0000000..156b3b4 --- /dev/null +++ b/src/main/java/com/main/ipks/service/errors/DownstreamBadResponseException.java @@ -0,0 +1,6 @@ +package com.main.ipks.service.errors; + +public class DownstreamBadResponseException extends RuntimeException { + public DownstreamBadResponseException(String message, Throwable cause) { super(message, cause); } + public DownstreamBadResponseException(String message) { super(message); } +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/service/errors/DownstreamBusinessPayloadException.java b/src/main/java/com/main/ipks/service/errors/DownstreamBusinessPayloadException.java new file mode 100644 index 0000000..b6502fe --- /dev/null +++ b/src/main/java/com/main/ipks/service/errors/DownstreamBusinessPayloadException.java @@ -0,0 +1,17 @@ +package com.main.ipks.service.errors; + +import org.springframework.http.HttpStatus; + +public class DownstreamBusinessPayloadException extends RuntimeException { + private final String rawJson; + private final HttpStatus statusToReturn; + + public DownstreamBusinessPayloadException(String rawJson, HttpStatus statusToReturn) { + super("Downstream business failure"); // message not exposed to client; rawJson is + this.rawJson = rawJson; + this.statusToReturn = statusToReturn; + } + + public String getRawJson() { return rawJson; } + public HttpStatus getStatusToReturn() { return statusToReturn; } +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/service/errors/DownstreamEmptyBodyException.java b/src/main/java/com/main/ipks/service/errors/DownstreamEmptyBodyException.java new file mode 100644 index 0000000..6b8e9f5 --- /dev/null +++ b/src/main/java/com/main/ipks/service/errors/DownstreamEmptyBodyException.java @@ -0,0 +1,5 @@ +package com.main.ipks.service.errors; + +public class DownstreamEmptyBodyException extends RuntimeException { + public DownstreamEmptyBodyException(String message) { super(message); } +} \ No newline at end of file diff --git a/src/main/java/com/main/ipks/service/errors/TokenFetchException.java b/src/main/java/com/main/ipks/service/errors/TokenFetchException.java new file mode 100644 index 0000000..33c3709 --- /dev/null +++ b/src/main/java/com/main/ipks/service/errors/TokenFetchException.java @@ -0,0 +1,6 @@ +package com.main.ipks.service.errors; + +public class TokenFetchException extends RuntimeException { + public TokenFetchException(String message, Throwable cause) { super(message, cause); } + public TokenFetchException(String message) { super(message); } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..782e408 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,22 @@ +spring.application.name=ipks + + + +logging.level.root=INFO +logging.level.com.main.ipks.service=DEBUG + + +# TOKEN +cedge.token.url=https://142.79.251.107:443/BANCEDGEAPI/api/getToken/HASAN + +# Api for CIF-Creation +cedge.cif.url=https://142.79.251.107:443/BANCEDGEAPI/api/cifCreation/HASAN + + +# Api for account creation + +# API1 +cedge.account.url=https://142.79.251.107:443/BANCEDGEAPI/api/depositsCreateDepositAccount/HASAN + +# API2 +cedge.accountOk.url=https://142.79.251.107:443/BANCEDGEAPI/api/createDepositAccountOk/HASAN \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..f57088d --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,46 @@ + + + + + + + + + ${LOG_PATH}/application.log + + + ${LOG_ARCHIVE}/application.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 60 + 20GB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/main/ipks_cbi/IpksCbiApplicationTests.java b/src/test/java/com/main/ipks_cbi/IpksCbiApplicationTests.java new file mode 100644 index 0000000..3298987 --- /dev/null +++ b/src/test/java/com/main/ipks_cbi/IpksCbiApplicationTests.java @@ -0,0 +1,13 @@ +package com.main.ipks; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class IpksCbiApplicationTests { + + @Test + void contextLoads() { + } + +}