diff --git a/.github/workflows/build-docker-mariadb.yml b/.github/workflows/build-docker-mariadb.yml index dfcbb52f2ed..0882613f803 100644 --- a/.github/workflows/build-docker-mariadb.yml +++ b/.github/workflows/build-docker-mariadb.yml @@ -16,17 +16,19 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - name: Build the image - run: ./gradlew :fineract-provider:clean :fineract-provider:jibDockerBuild -x test + run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:build :fineract-provider:jibDockerBuild -x test -x cucumber - name: Start the stack - run: docker-compose up -d + run: docker compose up -d - name: Wait for stack to come up - run: sleep 300 + run: sleep 500 + - name: Check the stack + run: docker ps - name: Check health run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health - name: Check info diff --git a/.github/workflows/build-docker-postgresql.yml b/.github/workflows/build-docker-postgresql.yml index ab927908138..53bb8d30db0 100644 --- a/.github/workflows/build-docker-postgresql.yml +++ b/.github/workflows/build-docker-postgresql.yml @@ -16,17 +16,19 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - name: Build the image - run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:jibDockerBuild -x test + run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:build :fineract-provider:jibDockerBuild -x test -x cucumber - name: Start the Standalone Stack - run: docker-compose -f docker-compose-postgresql.yml up -d + run: docker compose -f docker-compose-postgresql.yml up -d - name: Wait for stack to come up - run: sleep 300 + run: sleep 500 + - name: Check the stack + run: docker ps - name: Check health run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health - name: Check info diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml index 83982fafed0..eae03a170ac 100644 --- a/.github/workflows/build-documentation.yml +++ b/.github/workflows/build-documentation.yml @@ -15,12 +15,12 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 with: node-version: 16 - name: Congfigure vega-cli diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml index 0b76cb7eea4..691403b3715 100644 --- a/.github/workflows/build-mariadb.yml +++ b/.github/workflows/build-mariadb.yml @@ -10,7 +10,7 @@ jobs: services: mariad: - image: mariadb:11.1 + image: mariadb:11.2 ports: - 3306:3306 env: @@ -18,7 +18,7 @@ jobs: options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=5s --health-timeout=2s --health-retries=3 mock-oauth2-server: - image: ghcr.io/navikt/mock-oauth2-server:2.0.1 + image: ghcr.io/navikt/mock-oauth2-server:2.1.0 ports: - 9000:9000 env: @@ -34,12 +34,12 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 with: node-version: 16 - name: Congfigure vega-cli @@ -88,17 +88,18 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v3 with: name: test-results path: | + build/reports/ integration-tests/build/reports/ twofactor-tests/build/reports/ oauth2-tests/build/reports/ - name: Archive server logs if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v3 with: name: server-logs path: | diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml index 09c5079834c..683d3e00ee4 100644 --- a/.github/workflows/build-mysql.yml +++ b/.github/workflows/build-mysql.yml @@ -18,7 +18,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 mock-oauth2-server: - image: ghcr.io/navikt/mock-oauth2-server:2.0.1 + image: ghcr.io/navikt/mock-oauth2-server:2.1.0 ports: - 9000:9000 env: @@ -34,12 +34,12 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 with: node-version: 16 - name: Congfigure vega-cli @@ -88,17 +88,18 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v3 with: name: test-results path: | + build/reports/ integration-tests/build/reports/ twofactor-tests/build/reports/ oauth2-tests/build/reports/ - name: Archive server logs if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v3 with: name: server-logs path: | diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml index bcf577d098f..f657d9a5f80 100644 --- a/.github/workflows/build-postgresql.yml +++ b/.github/workflows/build-postgresql.yml @@ -19,7 +19,7 @@ jobs: options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3 mock-oauth2-server: - image: ghcr.io/navikt/mock-oauth2-server:2.0.1 + image: ghcr.io/navikt/mock-oauth2-server:2.1.0 ports: - 9000:9000 env: @@ -35,12 +35,12 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 with: node-version: 16 - name: Congfigure vega-cli @@ -89,17 +89,18 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v3 with: name: test-results path: | + build/reports/ integration-tests/build/reports/ twofactor-tests/build/reports/ oauth2-tests/build/reports/ - name: Archive server logs if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v3 with: name: server-logs path: | diff --git a/.github/workflows/smoke-activemq.yml b/.github/workflows/smoke-activemq.yml index 2880403886c..8f9455f0e98 100644 --- a/.github/workflows/smoke-activemq.yml +++ b/.github/workflows/smoke-activemq.yml @@ -16,17 +16,19 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - name: Build the image - run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:jibDockerBuild -x test + run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:build :fineract-provider:jibDockerBuild -x test -x cucumber - name: Start the ActiveMQ Stack - run: docker-compose -f docker-compose-postgresql-activemq.yml up --scale fineract-worker=1 -d + run: docker compose -f docker-compose-postgresql-activemq.yml up --scale fineract-worker=1 -d - name: Wait for stack to come up run: sleep 500 + - name: Check the stack + run: docker ps - name: Check health Manager run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health - name: Check health Worker1 diff --git a/.github/workflows/smoke-kafka.yml b/.github/workflows/smoke-kafka.yml index ce8bc912dbd..e614cdc138e 100644 --- a/.github/workflows/smoke-kafka.yml +++ b/.github/workflows/smoke-kafka.yml @@ -16,17 +16,19 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' cache: gradle - name: Build the image - run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:jibDockerBuild -x test + run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:build :fineract-provider:jibDockerBuild -x test -x cucumber - name: Start the Kafka Stack - run: docker-compose -f docker-compose-postgresql-kafka.yml up --scale fineract-worker=1 -d + run: docker compose -f docker-compose-postgresql-kafka.yml up --scale fineract-worker=1 -d - name: Wait for stack to come up run: sleep 500 + - name: Check the stack + run: docker ps - name: Check health Manager run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health - name: Check health Worker1 diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 94f7bb962a0..f240f1a68e0 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -24,7 +24,7 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 with: java-version: '17' distribution: 'zulu' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9eb23f95877..eb30dda5236 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # stale-issue-message: 'Stale issue message' diff --git a/.profileconfig.json b/.profileconfig.json new file mode 100644 index 00000000000..b2f42d90d09 --- /dev/null +++ b/.profileconfig.json @@ -0,0 +1,64 @@ +{ + "jfrConfig": { + "settings": "profile" + }, + "asyncProfilerConfig": { + "jfrsync": true, + "alloc": true, + "event": "wall", + "misc": "" + }, + "file": "$PROJECT_DIR/profile.jfr", + "conversionConfig": { + "nonProjectPackagePrefixes": [ + "java.", + "javax.", + "kotlin.", + "jdk.", + "com.google.", + "org.apache.", + "org.spring.", + "sun.", + "scala." + ], + "enableMarkers": true, + "initialVisibleThreads": 10, + "initialSelectedThreads": 10, + "includeGCThreads": false, + "includeInitialSystemProperty": false, + "includeInitialEnvironmentVariables": false, + "includeSystemProcesses": false, + "ignoredEvents": [ + "jdk.ActiveSetting", + "jdk.ActiveRecording", + "jdk.BooleanFlag", + "jdk.IntFlag", + "jdk.DoubleFlag", + "jdk.LongFlag", + "jdk.NativeLibrary", + "jdk.StringFlag", + "jdk.UnsignedIntFlag", + "jdk.UnsignedLongFlag", + "jdk.InitialSystemProperty", + "jdk.InitialEnvironmentVariable", + "jdk.SystemProcess", + "jdk.ModuleExport", + "jdk.ModuleRequire" + ], + "minRequiredItemsPerThread": 3 + }, + "additionalGradleTargets": [ + { + "targetPrefix": "quarkus", + "optionForVmArgs": "-Djvm.args", + "description": "Example quarkus config, adding profiling arguments via -Djvm.args option to the Gradle task run" + } + ], + "additionalMavenTargets": [ + { + "targetPrefix": "quarkus:", + "optionForVmArgs": "-Djvm.args", + "description": "Example quarkus config, adding profiling arguments via -Djvm.args option to the Maven goal run" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index d715dac941e..68991bcb83e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Apache Fineract: A Platform for Microfinance ============ -[![Swagger Validation](https://validator.swagger.io/validator?url=https://demo.fineract.dev/fineract-provider/swagger-ui/fineract.yaml)](https://validator.swagger.io/validator/debug?url=https://demo.fineract.dev/fineract-provider/swagger-ui/fineract.yaml) [![build](https://github.com/apache/fineract/actions/workflows/build.yml/badge.svg)](https://github.com/apache/fineract/actions/workflows/build.yml) [![Docker Hub](https://img.shields.io/docker/pulls/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract) [![Docker Build](https://img.shields.io/docker/cloud/build/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract/builds) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=apache_fineract&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=apache_fineract) +[![Swagger Validation](https://validator.swagger.io/validator?url=https://sandbox.mifos.community/fineract-provider/swagger-ui/fineract.yaml)](https://validator.swagger.io/validator/debug?url=https://sandbox.mifos.community/fineract-provider/swagger-ui/fineract.yaml) [![build](https://github.com/apache/fineract/actions/workflows/build.yml/badge.svg)](https://github.com/apache/fineract/actions/workflows/build.yml) [![Docker Hub](https://img.shields.io/docker/pulls/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract) [![Docker Build](https://img.shields.io/docker/cloud/build/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract/builds) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=apache_fineract&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=apache_fineract) @@ -22,15 +22,15 @@ If you are interested in contributing to this project, but perhaps don't quite k REQUIREMENTS ============ * `Java >= 17` (Azul Zulu JVM is tested by our CI on GitHub Actions) -* MariaDB `10.9` +* MariaDB `11.2` You can run the required version of the database server in a container, instead of having to install it, like this: - docker run --name mariadb-10.9 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:10.9 + docker run --name mariadb-11.2 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:11.2 and stop and destroy it like this: - docker rm -f mariadb-10.9 + docker rm -f mariadb-11.2
Beware that this database container database keeps its state inside the container and not on the host filesystem. It is lost when you destroy (rm) this container. This is typically fine for development. See [Caveats: Where to Store Data on the database container documentation](https://hub.docker.com/_/mariadb) re. how to make it persistent instead of ephemeral.
@@ -75,7 +75,7 @@ Run the following commands: ============ 1. Clone the repository or download and extract the archive file to your local directory. 2. Run `./gradlew clean bootJar` to build a modern cloud native fully self contained JAR file which will be created at `fineract-provider/build/libs` directory. -3. As we are not allowed to include a JDBC driver in the built JAR, download a JDBC driver of your choice. For example: `wget https://downloads.mariadb.com/Connectors/java/connector-java-2.7.5/mariadb-java-client-2.7.5.jar` +3. As we are not allowed to include a JDBC driver in the built JAR, download a JDBC driver of your choice. For example: `wget https://downloads.mariadb.com/Connectors/java/connector-java-3.3.2/mariadb-java-client-3.3.2.jar` 4. Start the jar and pass the directory where you have downloaded the JDBC driver as loader.path, for example: `java -Dloader.path=. -jar fineract-provider/build/libs/fineract-provider.jar` (does not require external Tomcat) NOTE: we cannot upgrade to version 3.0.x of the MariaDB driver just yet; have to wait until 3.0.4 is out for a bug fix. @@ -207,13 +207,16 @@ _(Note that in previous versions, the `mysqlserver` environment variable used at `docker run` time did something similar; this has changed in [FINERACT-773](https://issues.apache.org/jira/browse/FINERACT-773)), and the `mysqlserver` environment variable is now no longer supported.)_ -If you need the Java Flight Recorder image then copy the file from the running fineract-development container: +The logfiles and the Java Flight Recorder output are available in `PROJECT_ROOT/build/fineract/logs`. If you use IntelliJ then you can double-click on the `.jfr` file and open it with the IDE. You can also download Azul Mission Control from here https://www.azul.com/products/components/azul-mission-control/ to analyze the Java Flight Recorder file. + +NOTE: If you have issues with the file permissions and Docker Compose then you might need to change the variable values for `FINERACT_USER` and `FINERACT_GROUP` in `PROJECT_ROOT/config/docker/env/fineract-common.env`. You can find out what values you need to put there with the following commands: ``` -docker container cp fineract-development:/tmp/fineract.jfr /tmp +id -u ${USER} +id -u ${GROUP} ``` -NOTE: Download Azul Mission Control from here https://www.azul.com/products/components/azul-mission-control/ to analyze the Java Flight Recorder file. +Please make sure that you are not checking in your changed values. The defaults should normally work for most people. Connection pool configuration ============================= @@ -399,9 +402,9 @@ complies with the [Apache Software Foundation third-party license policy](https:

APACHE FINERACT PLATFORM API ============ -The API for Fineract is documented in [apiLive.htm](fineract-provider/src/main/resources/static/api-docs/apiLive.htm), and the [apiLive.htm can be viewed on Fineract.dev](https://fineract.apache.org/legacy-docs/apiLive.htm "API Documentation"). If you have your own Fineract instance running, you can find this documentation under [/fineract-provider/api-docs/apiLive.htm](https://localhost:8443/fineract-provider/api-docs/apiLive.htm). +The API for Fineract is documented in [apiLive.htm](fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm), and the [apiLive.htm can be viewed on fineract.apache.org](https://fineract.apache.org/docs/legacy/ "API Documentation"). If you have your own Fineract instance running, you can find this documentation under [/fineract-provider/legacy-docs/apiLive.htm](https://localhost:8443/fineract-provider/legacy-docs/apiLive.htm). -The Swagger documentation (work in progress; see [FINERACT-733](https://issues.apache.org/jira/browse/FINERACT-733)) can be accessed under [/fineract-provider/swagger-ui/index.html](https://localhost:8443/fineract-provider/swagger-ui/index.html) and [live Swagger UI here on Fineract.dev](https://demo.fineract.dev/fineract-provider/swagger-ui/index.html). +The Swagger documentation (work in progress; see [FINERACT-733](https://issues.apache.org/jira/browse/FINERACT-733)) can be accessed under [/fineract-provider/swagger-ui/index.html](https://localhost:8443/fineract-provider/swagger-ui/index.html) and [live Swagger UI here on Fineract.dev](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html). Apache Fineract supports client code generation using [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) based on the [OpenAPI Specification](https://swagger.io/specification/). For more instructions on how to generate the client code, check [docs/developers/swagger/client.md](docs/developers/swagger/client.md). @@ -418,7 +421,7 @@ Apache Fineract supports client code generation using [Swagger Codegen](https://
ONLINE DEMOS ============ -* [fineract.dev](https://www.fineract.dev) always runs the latest version of this code +* [sandbox.mifos.community](https://sandbox.mifos.community) always runs the latest version of this code * [demo.mifos.io](https://demo.mifos.io) A demo account is provided for users to experience the functionality of the Community App. Users can use "mifos" for USERNAME and "password" for PASSWORD (without quotation marks). * [Swagger-UI Demo video](https://www.youtube.com/watch?v=FlVd-0YAo6c) This is a demo video for Swagger-UI documentation, more information [here](https://github.com/apache/fineract#swagger-ui-documentation). @@ -443,7 +446,7 @@ Apache Fineract / Mifos X Demo (November 2016) - GORVENANCE AND POLICIES ======================= diff --git a/build.gradle b/build.gradle index 9b1a1799629..3ac3d744989 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -/** + /** * 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 @@ -61,7 +61,7 @@ buildscript { classpath 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' classpath 'com.google.cloud.tools:jib-layer-filter-extension-gradle:0.3.0' classpath 'org.apache.commons:commons-lang3:3.14.0' - classpath 'io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.11' + classpath 'io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.19' classpath 'jakarta.servlet:jakarta.servlet-api:6.0.0' } } @@ -70,14 +70,14 @@ plugins { id 'me.qoomon.git-versioning' version '6.4.3' id "org.barfuin.gradle.taskinfo" version "2.1.0" id 'com.adarshr.test-logger' version '4.0.0' - id 'com.diffplug.spotless' version '6.22.0' apply false + id 'com.diffplug.spotless' version '6.23.3' apply false id 'org.nosphere.apache.rat' version '0.8.1' apply false id 'com.github.hierynomus.license' version '0.16.1' apply false id 'com.github.jk1.dependency-license-report' version '2.5' apply false id 'org.zeroturnaround.gradle.jrebel' version '1.2.0' apply false - id 'org.springframework.boot' version '3.1.5' apply false + id 'org.springframework.boot' version '3.1.7' apply false id 'net.ltgt.errorprone' version '3.1.0' apply false - id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.2.11' apply false + id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.2.19' apply false id 'com.gorylenko.gradle-git-properties' version '2.4.1' apply false id 'org.asciidoctor.jvm.convert' version '3.3.2' apply false id 'org.asciidoctor.jvm.pdf' version '3.3.2' apply false @@ -88,9 +88,11 @@ plugins { id 'com.google.cloud.tools.jib' version '3.4.0' apply false id 'org.sonarqube' version '4.4.1.3373' id 'com.github.andygoossens.modernizer' version '1.9.0' apply false - id 'com.github.spotbugs' version '5.0.14' apply false + // TODO: upgrade to 6.0.4 + id 'com.github.spotbugs' version '5.0.15' apply false id 'se.thinkcode.cucumber-runner' version '0.0.11' apply false id "com.github.davidmc24.gradle.plugin.avro-base" version "1.9.1" apply false + id 'org.openapi.generator' version '7.2.0' apply false } apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle" @@ -119,7 +121,7 @@ gitVersioning.apply { } } -ext['groovy.version'] = '4.0.6' +ext['groovy.version'] = '4.0.17' ext['swaggerFile'] = "$rootDir/fineract-provider/build/classes/java/main/static/fineract.json".toString() allprojects { @@ -143,7 +145,7 @@ allprojects { spotless { format 'misc', { target '**/*.md', '**/*.properties', '**/.gitignore', '**/.openapi-generator-ignore', '**/*.yml', '**/*.xml', '**/**.json', '**/*.sql' - targetExclude '**/build/**', '**/bin/**', '**/.settings/**', '**/.idea/**', '**/.gradle/**', '**/gradlew.bat', '**/licenses/**', '**/banner.txt', '.vscode/**' + targetExclude '**/build/**', '**/bin/**', '**/.settings/**', '**/.idea/**', '**/.gradle/**', '**/gradlew.bat', '**/licenses/**', '**/banner.txt', '.vscode/**', '.profileconfig.json' indentWithSpaces(4) endWithNewline() trimTrailingWhitespace() @@ -321,6 +323,15 @@ configure(project.fineractJavaProjects) { sourceSets.main.output.resourcesDir = sourceSets.main.java.classesDirectory sourceSets.test.output.resourcesDir = sourceSets.test.java.classesDirectory + configurations.named('spotbugs').configure { + resolutionStrategy.eachDependency { + if (it.requested.group == 'org.ow2.asm') { + it.useVersion '9.5' + it.because "Asm 9.5 is required for JDK 21 support" + } + } + } + configurations { implementation.setCanBeResolved(true) api.setCanBeResolved(true) @@ -423,7 +434,7 @@ configure(project.fineractJavaProjects) { // Configuration for the Checkstyle plugin // https://docs.gradle.org/current/userguide/checkstyle_plugin.html dependencies { - checkstyle 'com.puppycrawl.tools:checkstyle:10.12.5' + checkstyle 'com.puppycrawl.tools:checkstyle:10.12.7' checkstyle 'com.github.sevntu-checkstyle:sevntu-checks:1.44.1' } @@ -445,7 +456,7 @@ configure(project.fineractJavaProjects) { // Configuration for the errorprone plugin // https://github.com/tbroyer/gradle-errorprone-plugin dependencies { - errorprone "com.google.errorprone:error_prone_core:2.23.0" + errorprone "com.google.errorprone:error_prone_core:2.24.0" } tasks.withType(JavaCompile) { @@ -600,7 +611,8 @@ configure(project.fineractJavaProjects) { // To generate an HTML report instead of XML spotbugs { - reportLevel = 'high' + // reportLevel = 'high' + // reportLevel = com.github.spotbugs.snom.Confidence.DEFAULT showProgress = false excludeFilter = file("$rootDir/config/spotbugs/exclude.xml") } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 4f609bbca6f..6f413c4e86e 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -58,4 +58,5 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit' implementation 'com.squareup.retrofit2:converter-jackson' implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'com.squareup.okhttp3:okhttp' } diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle index 29364efa803..d3f7ab31719 100644 --- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle +++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle @@ -22,18 +22,21 @@ apply plugin: 'io.spring.dependency-management' // https://github.com/spring-gradle-plugins/dependency-management-plugin dependencyManagement { imports { - mavenBom 'io.micrometer:micrometer-bom:1.12.0' - mavenBom 'org.springframework:spring-framework-bom:6.1.0' - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.1.5' - mavenBom 'io.awspring.cloud:spring-cloud-aws-dependencies:3.0.3' - mavenBom 'io.opentelemetry:opentelemetry-bom:1.32.0' - mavenBom 'org.jetbrains.kotlin:kotlin-bom:1.9.20' + mavenBom 'com.squareup.okhttp3:okhttp-bom:4.12.0' + mavenBom 'org.slf4j:slf4j-bom:2.0.10' + mavenBom 'io.micrometer:micrometer-bom:1.12.1' + mavenBom 'org.springframework:spring-framework-bom:6.1.2' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.1.7' + mavenBom 'io.awspring.cloud:spring-cloud-aws-dependencies:3.0.4' + mavenBom 'io.opentelemetry:opentelemetry-bom:1.33.0' + mavenBom 'org.jetbrains.kotlin:kotlin-bom:1.9.22' mavenBom 'org.junit:junit-bom:5.10.1' - mavenBom 'com.fasterxml.jackson:jackson-bom:2.16.0' - mavenBom 'io.cucumber:cucumber-bom:7.14.0' - mavenBom 'io.netty:netty-bom:4.1.101.Final' - mavenBom 'org.mockito:mockito-bom:5.7.0' - mavenBom 'software.amazon.awssdk:bom:2.21.28' + mavenBom 'com.fasterxml.jackson:jackson-bom:2.16.1' + mavenBom 'io.cucumber:cucumber-bom:7.15.0' + mavenBom 'io.netty:netty-bom:4.1.104.Final' + mavenBom 'org.mockito:mockito-bom:5.8.0' + mavenBom 'software.amazon.awssdk:bom:2.22.9' + mavenBom 'io.github.resilience4j:resilience4j-bom:2.2.0' } dependencies { @@ -41,37 +44,31 @@ dependencyManagement { // We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things. // We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available. - dependency 'org.slf4j:slf4j-api:2.0.9' - dependency 'org.slf4j:slf4j-simple:2.0.9' - dependency 'org.slf4j:jcl-over-slf4j:2.0.9' - dependency 'org.slf4j:jul-to-slf4j:2.0.9' - dependency 'org.slf4j:log4j-over-slf4j:2.0.9' - dependency 'ch.qos.logback:logback-core:1.4.11' - dependency 'ch.qos.logback:logback-classic:1.4.11' + dependency 'ch.qos.logback:logback-core:1.4.14' + dependency 'ch.qos.logback:logback-classic:1.4.14' dependency 'ch.qos.logback.contrib:logback-json-classic:0.1.5' dependency 'ch.qos.logback.contrib:logback-jackson:0.1.5' - dependency 'org.codehaus.janino:janino:3.1.10' + dependency 'org.codehaus.janino:janino:3.1.11' dependency 'org.eclipse.persistence:org.eclipse.persistence.jpa:4.0.0' dependency 'com.google.guava:guava:32.0.0-jre' dependency 'com.google.code.gson:gson:2.10.1' - dependency 'com.google.truth:truth:1.1.5' - dependency 'com.google.truth.extensions:truth-java8-extension:1.1.5' - dependency 'com.google.googlejavaformat:google-java-format:1.18.1' - dependency ('org.apache.commons:commons-email:1.5') { + dependency 'com.google.truth:truth:1.2.0' + dependency 'com.google.truth.extensions:truth-java8-extension:1.2.0' + dependency 'com.google.googlejavaformat:google-java-format:1.19.1' + dependency ('org.apache.commons:commons-email:1.6.0') { exclude 'com.sun.mail:javax.mail' exclude 'javax.activation:activation' } - dependency 'commons-io:commons-io:2.15.0' - dependency 'com.github.librepdf:openpdf:1.3.33' + dependency 'commons-io:commons-io:2.15.1' + dependency 'com.github.librepdf:openpdf:1.3.35' dependency ('org.mnode.ical4j:ical4j:3.2.14') { exclude 'com.sun.mail:javax.mail' exclude 'org.codehaus.groovy:groovy' } dependency 'org.apache.commons:commons-csv:1.10.0' dependency 'org.quartz-scheduler:quartz:2.3.2' - dependency 'software.amazon.awssdk:bom:2.21.28' dependency 'org.ehcache:ehcache:3.10.8' dependency 'com.github.spullara.mustache.java:compiler:0.9.11' dependency 'com.jayway.jsonpath:json-path:2.8.0' @@ -119,13 +116,14 @@ dependencyManagement { dependency 'jakarta.management.j2ee:jakarta.management.j2ee-api:1.1.4' dependency 'jakarta.jms:jakarta.jms-api:3.1.0' dependency 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' - dependency 'org.glassfish.jersey.media:jersey-media-multipart:3.1.3' + dependency 'org.glassfish.jersey.media:jersey-media-multipart:3.1.5' dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.6' // Swagger needs exactly this version dependency 'org.apache.bval:org.apache.bval.bundle:3.0.0' dependency 'joda-time:joda-time:2.12.5' dependency 'io.github.classgraph:classgraph:4.8.165' dependency 'org.awaitility:awaitility:4.2.0' + // TODO: upgrade to 4.8.3 dependency 'com.github.spotbugs:spotbugs-annotations:4.7.3' dependency 'javax.cache:cache-api:1.1.1' dependency 'org.mock-server:mockserver-junit-jupiter:5.15.0' @@ -148,16 +146,10 @@ dependencyManagement { dependency "com.squareup.retrofit2:converter-scalars:2.9.0" dependency "com.squareup.retrofit2:converter-gson:2.9.0" dependency "com.squareup.retrofit2:converter-protobuf:2.9.0" + dependency 'io.reactivex.rxjava2:rxjava:2.2.21' dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.common:1.0.1" dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.1" dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.httpclient4:1.0.1" - dependency "com.squareup.okhttp3:okhttp:4.12.0" - dependency "com.squareup.okhttp3:okcurl:4.12.0" - dependency "com.squareup.okhttp3:logging-interceptor:4.12.0" - dependency "com.squareup.okhttp3:okhttp-apache:4.9.3" - dependency "com.squareup.okhttp3:okhttp-android-support:4.9.3" - dependency "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" - dependency "com.squareup.okhttp3:okhttp-sse:4.12.0" dependency "io.gsonfire:gson-fire:1.9.0" dependency "com.google.code.findbugs:jsr305:3.0.2" dependency "commons-codec:commons-codec:1.16.0" @@ -168,44 +160,45 @@ dependencyManagement { dependency 'org.bouncycastle:bcprov-jdk15on:1.70' dependency 'org.bouncycastle:bcpg-jdk15on:1.70' - dependency 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' - dependency 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r' + dependency 'org.eclipse.jgit:org.eclipse.jgit:6.8.0.202311291450-r' + dependency 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r' dependency 'org.tmatesoft.svnkit:svnkit:1.10.11' dependency 'com.vdurmont:semver4j:3.1.0' dependency 'org.beryx:text-io:3.4.1' - dependency ('org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0') { + dependency ('org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0') { exclude 'io.swagger.core.v3:swagger-core' } - dependency 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.15.0' + dependency 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.15.1' - dependency ('org.apache.activemq:activemq-client:6.0.0') { + dependency ('org.apache.activemq:activemq-client:6.0.1') { exclude 'javax.annotation:javax.annotation-api' } - dependency 'io.swagger.core.v3:swagger-annotations-jakarta:2.2.11' - dependency ('io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.11') { + dependency 'io.swagger.core.v3:swagger-annotations-jakarta:2.2.19' + dependency ('io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.19') { exclude 'jakarta.activation:jakarta.activation-api' } - dependency ('io.swagger.core.v3:swagger-core-jakarta:2.2.11') { + dependency ('io.swagger.core.v3:swagger-core-jakarta:2.2.19') { exclude 'jakarta.activation:jakarta.activation-api' } - dependency "jakarta.annotation:jakarta.annotation-api:2.1.1" + dependency 'jakarta.annotation:jakarta.annotation-api:2.1.1' dependency 'jakarta.activation:jakarta.activation-api:2.1.2' dependency ('com.sun.mail:jakarta.mail:2.0.1') { // Spring needs this version exclude 'com.sun.activation:jakarta.activation' } - dependency ('jakarta.xml.bind:jakarta.xml.bind-api:4.0.0') { + dependency ('jakarta.xml.bind:jakarta.xml.bind-api:4.0.1') { exclude 'jakarta.activation:jakarta.activation-api' } - dependency ('org.liquibase:liquibase-core:4.23.0') { + dependency ('org.liquibase:liquibase-core:4.25.1') { exclude 'javax.xml.bind:jaxb-api' } + dependency 'org.liquibase.ext:liquibase-postgresql:4.25.1' dependency ('org.dom4j:dom4j:2.1.4') { exclude 'relaxngDatatype:relaxngDatatype' // already in com.sun.xml.bind:jaxb-osgi:2.3.0.1 @@ -214,38 +207,36 @@ dependencyManagement { exclude 'pull-parser:pull-parser' } - dependency 'org.owasp.esapi:esapi:2.5.2.0' + dependency 'org.owasp.esapi:esapi:2.5.3.1' dependency 'org.awaitility:awaitility:4.2.0' - dependencySet(group: 'org.apache.poi', version: '5.2.4') { + dependencySet(group: 'org.apache.poi', version: '5.2.5') { entry 'poi' entry 'poi-ooxml' entry 'poi-ooxml-schemas' } - dependencySet(group: 'io.rest-assured', version: '5.3.2') { + dependencySet(group: 'io.rest-assured', version: '5.4.0') { entry 'rest-assured' entry 'json-path' entry 'xml-path' } - dependency 'org.apache.groovy:groovy-xml:4.0.14' - dependency 'org.apache.groovy:groovy-json:4.0.15' + dependency 'org.apache.groovy:groovy-xml:4.0.17' + dependency 'org.apache.groovy:groovy-json:4.0.17' dependency 'org.mapstruct:mapstruct:1.5.5.Final' dependency 'org.mapstruct:mapstruct-processor:1.5.5.Final' dependency "org.apache.avro:avro:1.11.3" - dependency "io.github.resilience4j:resilience4j-spring-boot2:2.1.0" - - dependency ('org.mariadb.jdbc:mariadb-java-client:3.3.0') { + dependency ('org.mariadb.jdbc:mariadb-java-client:3.3.2') { exclude 'org.slf4j:jcl-over-slf4j' exclude 'org.slf4j:slf4j-api' } - dependency 'org.postgresql:postgresql:42.7.0' + dependency 'org.postgresql:postgresql:42.7.1' - dependency 'org.assertj:assertj-core:3.24.2' + dependency 'org.assertj:assertj-core:3.25.0' dependency 'org.apache.commons:commons-math3:3.6.1' diff --git a/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/EmailService.groovy b/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/EmailService.groovy index 6b691541050..ac93770bdcb 100644 --- a/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/EmailService.groovy +++ b/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/EmailService.groovy @@ -73,8 +73,8 @@ class EmailService { if(params.bcc) { msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(params.bcc)) } - msg.setSubject(params.subject) - msg.setText(params.message); + msg.setSubject(params.subject, "UTF-8") + msg.setText(params.message, "UTF-8"); Transport.send(msg); diff --git a/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/TemplateService.groovy b/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/TemplateService.groovy index 356c9bb054b..bcb8f72650a 100644 --- a/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/TemplateService.groovy +++ b/buildSrc/src/main/groovy/org/apache/fineract/gradle/service/TemplateService.groovy @@ -27,6 +27,8 @@ import org.apache.fineract.gradle.FineractPluginExtension import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.nio.charset.Charset + class TemplateService { private static final Logger log = LoggerFactory.getLogger(TemplateService.class) @@ -35,7 +37,7 @@ class TemplateService { TemplateService(FineractPluginExtension.FineractPluginConfigTemplate config) { def dir = new File(config.templateDir); - this.config = new Configuration(Configuration.VERSION_2_3_31); + this.config = new Configuration(Configuration.VERSION_2_3_32); this.config.setDirectoryForTemplateLoading(dir) this.config.setDefaultEncoding("UTF-8"); this.config.setLogTemplateExceptions(false); @@ -58,7 +60,7 @@ class TemplateService { Template template = null; if(params.templateFile) { - template = new Template("template", new FileReader(new File(params.templateFile)), this.config) + template = new Template("template", new FileReader(new File(params.templateFile), Charset.forName("UTF-8")), this.config) } if(params.template) { template = new Template("template", new StringReader(params.template), this.config) @@ -69,7 +71,7 @@ class TemplateService { def output = new File(params.outputFile) output.createNewFile() - template.process(data, new FileWriter(output, false)) + template.process(data, new FileWriter(output, Charset.forName("UTF-8"), false)) } else { def output = new StringWriter() diff --git a/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl b/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl index 4c10f083ae0..83f804a5f52 100644 --- a/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl +++ b/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl @@ -36,4 +36,4 @@ ${project['fineract.config.name']} -πŸŽ‰ Powered by Fineract Release Plugin 🎊 \ No newline at end of file +πŸŽ‰ Powered by Fineract Release Plugin 🎊 diff --git a/buildSrc/src/main/resources/email/release.step01.headsup.subject.ftl b/buildSrc/src/main/resources/email/release.step01.headsup.subject.ftl index 3a1e4751f04..71df28ea450 100644 --- a/buildSrc/src/main/resources/email/release.step01.headsup.subject.ftl +++ b/buildSrc/src/main/resources/email/release.step01.headsup.subject.ftl @@ -18,4 +18,4 @@ under the License. --> -[FINERACT] [PROPOSAL] πŸ“¦ New release ${project['fineract.release.version']} \ No newline at end of file +[FINERACT] [PROPOSAL] πŸ“¦ New release ${project['fineract.release.version']} diff --git a/buildSrc/src/main/resources/email/release.step03.branch.subject.ftl b/buildSrc/src/main/resources/email/release.step03.branch.subject.ftl index 0a7f333dc7b..85fea033aba 100644 --- a/buildSrc/src/main/resources/email/release.step03.branch.subject.ftl +++ b/buildSrc/src/main/resources/email/release.step03.branch.subject.ftl @@ -18,4 +18,4 @@ under the License. --> -[FINERACT] [ANNOUNCE] πŸ”€ ${project['fineract.release.version']} release branch \ No newline at end of file +[FINERACT] [ANNOUNCE] πŸ”€ ${project['fineract.release.version']} release branch diff --git a/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl b/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl index adb471fe45c..9c8df50844e 100644 --- a/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl +++ b/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl @@ -18,4 +18,4 @@ under the License. --> -[FINERACT] [VOTE] πŸ—³οΈ ${project['fineract.release.version']} for release \ No newline at end of file +[FINERACT] [VOTE] πŸ—³οΈ ${project['fineract.release.version']} for release diff --git a/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl b/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl index 9dfa8ec5a34..38092660527 100644 --- a/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl +++ b/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl @@ -18,4 +18,4 @@ under the License. --> -[FINERACT] [VOTE] [RESULT] 🧾️ ${project['fineract.release.version']} for release \ No newline at end of file +[FINERACT] [VOTE] [RESULT] 🧾️ ${project['fineract.release.version']} for release diff --git a/buildSrc/src/main/resources/email/release.step15.announce.message.ftl b/buildSrc/src/main/resources/email/release.step15.announce.message.ftl index f6d4bfb0f3a..e08c44dc285 100644 --- a/buildSrc/src/main/resources/email/release.step15.announce.message.ftl +++ b/buildSrc/src/main/resources/email/release.step15.announce.message.ftl @@ -40,4 +40,4 @@ https://issues.apache.org/jira/secure/ReleaseNote.jspa?version=${project['finera For more information on Apache Fineract please visit project home page: https://fineract.apache.org -The Apache Fineract Team \ No newline at end of file +The Apache Fineract Team diff --git a/buildSrc/src/main/resources/email/release.step15.announce.subject.ftl b/buildSrc/src/main/resources/email/release.step15.announce.subject.ftl index 2701e86badc..41d04d822c7 100644 --- a/buildSrc/src/main/resources/email/release.step15.announce.subject.ftl +++ b/buildSrc/src/main/resources/email/release.step15.announce.subject.ftl @@ -18,4 +18,4 @@ under the License. --> -[ANNOUNCE] Apache Fineract ${project['fineract.release.version']} Release \ No newline at end of file +[ANNOUNCE] Apache Fineract ${project['fineract.release.version']} Release diff --git a/buildSrc/src/main/resources/git/release.step05.tag.message.ftl b/buildSrc/src/main/resources/git/release.step05.tag.message.ftl index 645090334be..8dbf0bd7e55 100644 --- a/buildSrc/src/main/resources/git/release.step05.tag.message.ftl +++ b/buildSrc/src/main/resources/git/release.step05.tag.message.ftl @@ -18,4 +18,4 @@ under the License. --> -Fineract ${project['fineract.release.version']} release \ No newline at end of file +Fineract ${project['fineract.release.version']} release diff --git a/config/docker/compose/activemq.yml b/config/docker/compose/activemq.yml index e60c40f9380..80c6699f5b2 100644 --- a/config/docker/compose/activemq.yml +++ b/config/docker/compose/activemq.yml @@ -22,5 +22,5 @@ services: activemq: image: symptoma/activemq:5.18.3 ports: - - 6161:6161 - - 61616:61616 + - "6161:6161" + - "61616:61616" diff --git a/config/docker/compose/fineract-custom.yml b/config/docker/compose/fineract-custom.yml new file mode 100644 index 00000000000..d46ef411b2d --- /dev/null +++ b/config/docker/compose/fineract-custom.yml @@ -0,0 +1,32 @@ +# 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. +# + +version: "3.8" + +services: + fineract: + # user: "${FINERACT_USER}:${FINERACT_GROUP}" + image: fineract-custom:latest + volumes: + - ${PWD}/config/docker/logback/logback-override.xml:/app/logback-override.xml + - ${PWD}/config/docker/aws/etc/credentials:/etc/aws/credentials:ro + - ${PWD}/build/fineract/logs:/var/logs/fineract:rw + healthcheck: + test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "localhost" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ] + timeout: 1s + retries: 60 diff --git a/config/docker/compose/fineract.yml b/config/docker/compose/fineract.yml index e4a3be880aa..ae5323c41c6 100644 --- a/config/docker/compose/fineract.yml +++ b/config/docker/compose/fineract.yml @@ -20,12 +20,13 @@ version: "3.8" services: fineract: + # user: "${FINERACT_USER}:${FINERACT_GROUP}" image: fineract:latest volumes: - - ../aws/etc/credentials:/etc/aws/credentials + - ${PWD}/config/docker/logback/logback-override.xml:/app/logback-override.xml + - ${PWD}/config/docker/aws/etc/credentials:/etc/aws/credentials:ro + - ${PWD}/build/fineract/logs:/var/logs/fineract:rw healthcheck: - test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "fineract-server" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ] - timeout: 10s - retries: 10 - env_file: - - ../env/fineract-common.env + test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "localhost" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ] + timeout: 1s + retries: 60 diff --git a/config/docker/compose/mariadb.yml b/config/docker/compose/mariadb.yml index 75a5483bc13..2c56ec15553 100644 --- a/config/docker/compose/mariadb.yml +++ b/config/docker/compose/mariadb.yml @@ -13,22 +13,20 @@ # "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. -# - version: "3.8" services: mariadb: - image: mariadb:11.0 + container_name: mariadb + image: mariadb:11.2 volumes: - - ../mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf - - ../mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro + - ${PWD}/config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro + - ${PWD}/config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro restart: always env_file: - - ../env/mysql.env + - ${PWD}/config/docker/env/mysql.env healthcheck: - test: ["CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized"] + test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ] timeout: 10s retries: 10 ports: diff --git a/config/docker/compose/observability.yml b/config/docker/compose/observability.yml index dc1f6d41f27..de59e1870ff 100644 --- a/config/docker/compose/observability.yml +++ b/config/docker/compose/observability.yml @@ -31,31 +31,35 @@ version: "3.8" services: loki: + container_name: loki image: grafana/loki:2.9.2 command: -config.file=/etc/loki/local-config.yaml ports: - "3100:3100" prometheus: + container_name: prometheus image: prom/prometheus:v2.47.2 command: '--config.file=/etc/prometheus/prometheus.yml' ports: - "9090:9090" volumes: - - ../prometheus/etc:/etc/prometheus + - ${PWD}/config/docker/prometheus/etc:/etc/prometheus logging: *default-logging grafana: + container_name: grafana image: grafana/grafana-oss:10.2.0 ports: - "3000:3000" volumes: - - ../grafana/datasources:/etc/grafana/provisioning/datasources - - ../grafana/etc/dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yaml - - ../grafana/dashboards:/var/lib/grafana/dashboards + - ${PWD}/config/docker/grafana/datasources:/etc/grafana/provisioning/datasources + - ${PWD}/config/docker/grafana/etc/dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yaml + - ${PWD}/config/docker/grafana/dashboards:/var/lib/grafana/dashboards logging: *default-logging tempo: + container_name: tempo image: grafana/tempo:2.2.4 # command: [ "--target=all", "--storage.trace.backend=local", "--storage.trace.local.path=/tmp/tempo", "--auth.enabled=false" ] command: [ "-config.file=/etc/tempo.yml" ] @@ -67,6 +71,6 @@ services: - "4318:4318" # otlp http - "9411:9411" # zipkin volumes: - - ../tempo/etc/tempo.yml:/etc/tempo.yml + - ${PWD}/config/docker/tempo/etc/tempo.yml:/etc/tempo.yml - /tmp/tempo:/tmp/tempo logging: *default-logging diff --git a/config/docker/compose/postgresql.yml b/config/docker/compose/postgresql.yml index 4c87ba90f88..878c88264fd 100644 --- a/config/docker/compose/postgresql.yml +++ b/config/docker/compose/postgresql.yml @@ -20,12 +20,12 @@ version: "3.8" services: postgresql: - image: postgres:16.0 + image: postgres:16.1 volumes: - - ../postgresql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d/:Z,ro + - ${PWD}/config/docker/postgresql/docker-entrypoint-initdb.d/01-init.sh:/docker-entrypoint-initdb.d/01-init.sh:Z,ro restart: always env_file: - - ../env/postgresql.env + - ${PWD}/config/docker/env/postgresql.env healthcheck: test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "root" ] timeout: 10s diff --git a/config/docker/env/debug.env b/config/docker/env/debug.env index fcddc452e99..34023e558ad 100644 --- a/config/docker/env/debug.env +++ b/config/docker/env/debug.env @@ -18,4 +18,5 @@ # # see here for more details related to JFR: https://access.redhat.com/documentation/en-us/red_hat_build_of_openjdk/8/html/using_jdk_flight_recorder_with_red_hat_build_of_openjdk/configure-jfr-options -JAVA_TOOL_OPTIONS="-Xmx2G -Xms2G -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5000 -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -XX:StartFlightRecording=delay=20s,duration=60s,disk=true,name=fineract,dumponexit=true,filename=/tmp/fineract.jfr,settings=profile -Xlog:jfr=info" +# JAVA_TOOL_OPTIONS="-Xmx2G -Xms2G -Dlogging.config=/app/logback-override.xml -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5000 -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -XX:StartFlightRecording=delay=20s,duration=60s,disk=true,name=fineract,path-to-gc-roots=true,dumponexit=true,filename=/var/logs/fineract/fineract.jfr,settings=profile -Xlog:jfr=info" +JAVA_TOOL_OPTIONS="-Xmx2G -Xms2G -Dlogging.config=/app/logback-override.xml -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5000 -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -Xlog:jfr=info" diff --git a/config/docker/env/fineract-common.env b/config/docker/env/fineract-common.env index cff62452c0b..a1e5ad745c4 100644 --- a/config/docker/env/fineract-common.env +++ b/config/docker/env/fineract-common.env @@ -18,6 +18,8 @@ # # ... following variables are optional; "application.properties" contains reasonable defaults (same as here) +FINERACT_USER=1000 +FINERACT_GROUP=1000 FINERACT_HIKARI_MINIMUM_IDLE=3 FINERACT_HIKARI_MAXIMUM_POOL_SIZE=10 FINERACT_HIKARI_IDLE_TIMEOUT=60000 @@ -25,6 +27,8 @@ FINERACT_HIKARI_CONNECTION_TIMEOUT=20000 FINERACT_HIKARI_TEST_QUERY=SELECT 1 FINERACT_HIKARI_AUTO_COMMIT=true FINERACT_SERVER_SSL_ENABLED=true +FINERACT_HIKARI_USERNAME=root +FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS=true FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE=250 FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT=2048 @@ -37,14 +41,19 @@ FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS=true FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS=false FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES=true FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION=true +# NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to create the default tenant database FINERACT_DEFAULT_TENANTDB_CONN_PARAMS= FINERACT_DEFAULT_TENANTDB_TIMEZONE=Asia/Kolkata FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default FINERACT_DEFAULT_TENANTDB_NAME=fineract_default FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant +FINERACT_DEFAULT_TENANTDB_HOSTNAME=db +FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca +FINERACT_DEFAULT_MASTER_PASSWORD=fineract FINERACT_MANAGEMENT_ENDPOINT_WEB_EXPOSURE_INCLUDE=health,info,prometheus FINERACT_MANAGEMENT_METRICS_TAGS_APPLICATION=fineract FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=false +FINERACT_INSECURE_HTTP_CLIENT=true SPRING_PROFILES_ACTIVE=test,diagnostics OTEL_SERVICE_NAME=fineract -JAVA_TOOL_OPTIONS="-Xmx1G -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication" +JAVA_TOOL_OPTIONS="-Xmx1G -XX:MinRAMPercentage=25 -XX:MaxRAMPercentage=80 -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED" diff --git a/config/docker/env/fineract-mariadb.env b/config/docker/env/fineract-mariadb.env index 6a9bc7d541f..af84f543d10 100644 --- a/config/docker/env/fineract-mariadb.env +++ b/config/docker/env/fineract-mariadb.env @@ -20,10 +20,6 @@ # NOTE: env vars prefixed "FINERACT_HIKARI_*" are used to configure the database connection pool FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME=org.mariadb.jdbc.Driver FINERACT_HIKARI_JDBC_URL=jdbc:mariadb://db:3306/fineract_tenants -FINERACT_HIKARI_USERNAME=root -FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca # NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to create the default tenant database -FINERACT_DEFAULT_TENANTDB_HOSTNAME=db FINERACT_DEFAULT_TENANTDB_PORT=3306 FINERACT_DEFAULT_TENANTDB_UID=root -FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca diff --git a/config/docker/env/fineract-postgresql.env b/config/docker/env/fineract-postgresql.env index 99c5355ae31..1fb152da93c 100644 --- a/config/docker/env/fineract-postgresql.env +++ b/config/docker/env/fineract-postgresql.env @@ -23,7 +23,5 @@ FINERACT_HIKARI_JDBC_URL=jdbc:postgresql://db:5432/fineract_tenants FINERACT_HIKARI_USERNAME=postgres FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca # NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to create the default tenant database -FINERACT_DEFAULT_TENANTDB_HOSTNAME=db FINERACT_DEFAULT_TENANTDB_PORT=5432 FINERACT_DEFAULT_TENANTDB_UID=postgres -FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca diff --git a/config/docker/env/fineract-worker.env b/config/docker/env/fineract-worker.env index 0245b5a13b5..e7ad8555c3f 100644 --- a/config/docker/env/fineract-worker.env +++ b/config/docker/env/fineract-worker.env @@ -21,3 +21,4 @@ FINERACT_NODE_ID=2 FINERACT_MODE_BATCH_MANAGER_ENABLED=false FINERACT_MODE_BATCH_WORKER_ENABLED=true OTEL_SERVICE_NAME=fineract-worker +FINERACT_LIQUIBASE_ENABLED=false \ No newline at end of file diff --git a/config/docker/logback/logback-override.xml b/config/docker/logback/logback-override.xml new file mode 100644 index 00000000000..4c5871f767f --- /dev/null +++ b/config/docker/logback/logback-override.xml @@ -0,0 +1,61 @@ + + + + + + + false + + + + false + + %green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n + + + + + /var/logs/fineract/fineract.log + true + true + + /var/logs/fineract/fineract.%d{yyyy-MM-dd}.log + 2 + 100MB + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + + + + diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index b5c1ed495df..251015bb617 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -26,4 +26,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/custom/docker/build.gradle b/custom/docker/build.gradle index 8938e3d32bb..28b9415f366 100644 --- a/custom/docker/build.gradle +++ b/custom/docker/build.gradle @@ -44,22 +44,6 @@ jib { container { creationTime = 'USE_CURRENT_TIMESTAMP' mainClass = 'org.apache.fineract.ServerApplication' - jvmFlags = [ - '-Xms1G', - '-XshowSettings:vm', - '-XX:+UseContainerSupport', - '-XX:+UseStringDeduplication', - '-XX:MinRAMPercentage=25', - '-XX:MaxRAMPercentage=80', - '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED', - '--add-opens=java.base/java.lang=ALL-UNNAMED', - '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED', - '--add-opens=java.base/java.io=ALL-UNNAMED', - '--add-opens=java.base/java.security=ALL-UNNAMED', - '--add-opens=java.base/java.util=ALL-UNNAMED', - '--add-opens=java.management/javax.management=ALL-UNNAMED', - '--add-opens=java.naming/javax.naming=ALL-UNNAMED' - ] args = [ '-Duser.home=/tmp', '-Dfile.encoding=UTF-8', @@ -111,5 +95,3 @@ jib { } } } - -// tasks.jibDockerBuild.dependsOn(':fineract-provider:bootJar') diff --git a/custom/docker/docker-compose.yml b/custom/docker/docker-compose.yml deleted file mode 100644 index ae5b69d0000..00000000000 --- a/custom/docker/docker-compose.yml +++ /dev/null @@ -1,87 +0,0 @@ -# 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. -# - -# You can replace and test a more recent version of docker compose -version: '3.7' -services: - # Backend service - fineractmysql: - image: mariadb:11.1 - volumes: - - ../../fineract-db/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf - - ../../fineract-db/docker:/docker-entrypoint-initdb.d:Z,ro - restart: always - environment: - MYSQL_ROOT_PASSWORD: skdcnwauicn2ucnaecasdsajdnizucawencascdca - healthcheck: - test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--password=skdcnwauicn2ucnaecasdsajdnizucawencascdca" ] - timeout: 10s - retries: 10 - ports: - - "3306:3306" - fineract-server: - image: fineract-custom:latest - volumes: - - ../../fineract-provider/build/data:/data - healthcheck: - test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "fineract-server" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ] - timeout: 10s - retries: 10 - ports: - - 8443:8443 - depends_on: - fineractmysql: - condition: service_healthy - environment: - # NOTE: node aware scheduler - - FINERACT_NODE_ID=1 - # NOTE: env vars prefixed "FINERACT_HIKARI_*" are used to configure the database connection pool - - FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME=org.mariadb.jdbc.Driver - - FINERACT_HIKARI_JDBC_URL=jdbc:mariadb://fineractmysql:3306/fineract_tenants - - FINERACT_HIKARI_USERNAME=root - - FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca - # ... following variables are optional; "application.properties" contains reasonable defaults (same as here) - - FINERACT_HIKARI_MINIMUM_IDLE=3 - - FINERACT_HIKARI_MAXIMUM_POOL_SIZE=10 - - FINERACT_HIKARI_IDLE_TIMEOUT=60000 - - FINERACT_HIKARI_CONNECTION_TIMEOUT=20000 - - FINERACT_HIKARI_TEST_QUERY=SELECT 1 - - FINERACT_HIKARI_AUTO_COMMIT=true - - FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS=true - - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE=250 - - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT=2048 - - FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS=true - - FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE=true - - FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS=true - - FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA=true - - FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION=true - - FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS=true - - FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS=false - - FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES=true - - FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION=true - # NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to create the default tenant database - - FINERACT_DEFAULT_TENANTDB_HOSTNAME=fineractmysql - - FINERACT_DEFAULT_TENANTDB_PORT=3306 - - FINERACT_DEFAULT_TENANTDB_UID=root - - FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca - - FINERACT_DEFAULT_TENANTDB_CONN_PARAMS= - - FINERACT_DEFAULT_TENANTDB_TIMEZONE=Asia/Kolkata - - FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default - - FINERACT_DEFAULT_TENANTDB_NAME=fineract_default - - FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant - - JAVA_TOOL_OPTIONS="-Xmx1G" diff --git a/docker-compose-custom.yml b/docker-compose-custom.yml new file mode 100644 index 00000000000..ecb2d72ecda --- /dev/null +++ b/docker-compose-custom.yml @@ -0,0 +1,39 @@ +# 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. +# + +# You can replace and test a more recent version of docker compose +version: "3.8" +services: + db: + extends: + file: ./config/docker/compose/mariadb.yml + service: mariadb + + fineract: + extends: + file: ./config/docker/compose/fineract-custom.yml + service: fineract + ports: + - "8443:8443" + depends_on: + db: + condition: service_healthy + env_file: + - ./config/docker/env/fineract.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-mariadb.env diff --git a/docker-compose-development.yml b/docker-compose-development.yml index f08937072f0..ddaf50c9942 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -79,8 +79,9 @@ services: condition: service_started env_file: - ./config/docker/env/aws.env - - ./config/docker/env/fineract-mariadb.env - ./config/docker/env/fineract.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-mariadb.env - ./config/docker/env/tracing.env - ./config/docker/env/oltp.env - ./config/docker/env/prometheus.env diff --git a/docker-compose-postgresql-activemq.yml b/docker-compose-postgresql-activemq.yml index 2055a5426bf..7e5961f3ea3 100644 --- a/docker-compose-postgresql-activemq.yml +++ b/docker-compose-postgresql-activemq.yml @@ -39,8 +39,9 @@ services: db: condition: service_healthy env_file: - - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/fineract-manager.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/activemq.env fineract-worker: @@ -53,9 +54,10 @@ services: ports: - "8444-8445:8443" depends_on: - db: + fineract-manager: condition: service_healthy env_file: - - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/fineract-worker.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/activemq.env diff --git a/docker-compose-postgresql-kafka-msk.yml b/docker-compose-postgresql-kafka-msk.yml index 3e1cfa0afc8..8ca2cef0f35 100644 --- a/docker-compose-postgresql-kafka-msk.yml +++ b/docker-compose-postgresql-kafka-msk.yml @@ -19,7 +19,6 @@ version: "3.8" services: - db: extends: file: ./config/docker/compose/postgresql.yml @@ -35,8 +34,10 @@ services: db: condition: service_healthy env_file: - - ./config/docker/env/aws.env - ./config/docker/env/fineract-manager.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env + - ./config/docker/env/aws.env - ./config/docker/env/kafka-client-msk.env fineract-worker: @@ -49,9 +50,11 @@ services: ports: - "8444-8445:8443" depends_on: - db: + fineract-manager: condition: service_healthy env_file: - - ./config/docker/env/aws.env - ./config/docker/env/fineract-worker.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env + - ./config/docker/env/aws.env - ./config/docker/env/kafka-client-msk.env diff --git a/docker-compose-postgresql-kafka.yml b/docker-compose-postgresql-kafka.yml index c0fbb895a40..92e6cf2a01c 100644 --- a/docker-compose-postgresql-kafka.yml +++ b/docker-compose-postgresql-kafka.yml @@ -17,12 +17,12 @@ # # You can replace and test a more recent version of docker compose -version: '3.7' +version: "3.7" services: kafka: - image: 'bitnami/kafka:3.5.1-debian-11-r7' + image: "bitnami/kafka:3.5.1-debian-11-r7" ports: - - '9092:9092' + - "9092:9092" env_file: - ./config/docker/env/kafka-server.env @@ -40,9 +40,12 @@ services: depends_on: db: condition: service_healthy + kafka: + condition: service_started env_file: - - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/fineract-manager.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/kafka-client.env fineract-worker: @@ -55,9 +58,10 @@ services: ports: - "8444-8445:8443" depends_on: - db: + fineract-manager: condition: service_healthy env_file: - - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/fineract-worker.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/kafka-client.env diff --git a/docker-compose-postgresql.yml b/docker-compose-postgresql.yml index e6a5690b6f1..181fb6f7c8a 100644 --- a/docker-compose-postgresql.yml +++ b/docker-compose-postgresql.yml @@ -35,5 +35,6 @@ services: db: condition: service_healthy env_file: - - ./config/docker/env/fineract-postgresql.env - ./config/docker/env/fineract.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-postgresql.env diff --git a/docker-compose.yml b/docker-compose.yml index 17417a6278b..d67579612e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,5 +35,6 @@ services: db: condition: service_healthy env_file: - - ./config/docker/env/fineract-mariadb.env - ./config/docker/env/fineract.env + - ./config/docker/env/fineract-common.env + - ./config/docker/env/fineract-mariadb.env diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc index 95d504c54b4..53fca426862 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc @@ -52,11 +52,15 @@ ] }, { + "default": null, "name": "installmentDelinquencyBuckets", - "type": { - "type": "array", - "items": "org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1" - } + "type": [ + "null", + { + "type": "array", + "items": "org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1" + } + ] } ] } diff --git a/fineract-client/build.gradle b/fineract-client/build.gradle index 5c7bc65318c..6fd0185a829 100644 --- a/fineract-client/build.gradle +++ b/fineract-client/build.gradle @@ -16,9 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -plugins { - id 'org.openapi.generator' version '6.6.0' -} +apply plugin: 'org.openapi.generator' description = 'Fineract Client' apply from: 'dependencies.gradle' diff --git a/fineract-client/dependencies.gradle b/fineract-client/dependencies.gradle index 62f7b8b5808..e5bf65e9c98 100644 --- a/fineract-client/dependencies.gradle +++ b/fineract-client/dependencies.gradle @@ -21,7 +21,6 @@ dependencies { // only handles javax annotations, not jakarta ones implementation 'jakarta.annotation:jakarta.annotation-api:1.3.5' - implementation( 'io.swagger.core.v3:swagger-annotations-jakarta', 'com.squareup.retrofit2:retrofit', @@ -31,6 +30,8 @@ dependencies { 'com.squareup.retrofit2:converter-gson', 'io.gsonfire:gson-fire', 'com.google.code.findbugs:jsr305', + 'com.github.spotbugs:spotbugs-annotations', + 'com.squareup.okhttp3:okhttp', 'com.squareup.okhttp3:logging-interceptor', ) diff --git a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java index 316240bb411..0a3d7ec96a8 100644 --- a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java +++ b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java @@ -22,6 +22,8 @@ import org.apache.fineract.client.models.RetrieveOneResponse; import org.apache.fineract.client.util.Calls; import org.apache.fineract.client.util.FineractClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Demo code which is included in the fineract-doc/src/docs/en/05_client.adoc. @@ -33,12 +35,15 @@ */ public class FineractClientDemo { + private static final Logger log = LoggerFactory.getLogger(FineractClientDemo.class); + void demoClient() { // tag::documentation[] FineractClient fineract = FineractClient.builder().baseURL("https://demo.fineract.dev/fineract-provider/api/v1/").tenant("default") .basicAuth("mifos", "password").build(); List staff = Calls.ok(fineract.staff.retrieveAll16(1L, true, false, "ACTIVE")); String name = staff.get(0).getDisplayName(); + log.info("Display name: {}", name); // end::documentation[] } diff --git a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTechnicalTest.java b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTechnicalTest.java index b369bec3a69..f45b3712f47 100644 --- a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTechnicalTest.java +++ b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTechnicalTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.fineract.client.util.FineractClient; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -29,6 +30,7 @@ * * @author Michael Vorburger.ch */ +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class FineractClientTechnicalTest { @Test diff --git a/fineract-core/dependencies.gradle b/fineract-core/dependencies.gradle index 3bbde9d033d..b7bab1d2c78 100644 --- a/fineract-core/dependencies.gradle +++ b/fineract-core/dependencies.gradle @@ -53,7 +53,7 @@ dependencies { 'org.springdoc:springdoc-openapi-starter-webmvc-ui', 'org.mapstruct:mapstruct', - 'io.github.resilience4j:resilience4j-spring-boot2', + 'io.github.resilience4j:resilience4j-spring-boot3', 'org.apache.httpcomponents:httpcore', ) implementation ('org.springframework.boot:spring-boot-starter-data-jpa') { diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResource.java b/fineract-core/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResource.java index 95c8d4a7e76..e9d2e545b6c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResource.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResource.java @@ -70,7 +70,7 @@ public class GLClosuresApiResource { Arrays.asList("id", "officeId", "officeName", "closingDate", "deleted", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId", "lastUpdatedByUsername")); - private final String resourceNameForPermission = "GLCLOSURE"; + private static final String RESOURCE_NAME_FOR_PERMISSION = "GLCLOSURE"; private final PlatformSecurityContext context; private final GLClosureReadPlatformService glClosureReadPlatformService; @@ -88,7 +88,7 @@ public class GLClosuresApiResource { public String retrieveAllClosures(@Context final UriInfo uriInfo, @QueryParam("officeId") @Parameter(name = "officeId") final Long officeId) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final List glClosureDatas = this.glClosureReadPlatformService.retrieveAllGLClosures(officeId); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); @@ -106,7 +106,7 @@ public String retrieveAllClosures(@Context final UriInfo uriInfo, public String retreiveClosure(@PathParam("glClosureId") @Parameter(description = "glClosureId") final Long glClosureId, @Context final UriInfo uriInfo) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java index e4b51b092ee..2bbd839dda6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java @@ -85,7 +85,7 @@ public class GLAccountsApiResource { "allowedAssetsTagOptions", "allowedLiabilitiesTagOptions", "allowedEquityTagOptions", "allowedIncomeTagOptions", "allowedExpensesTagOptions", "creditAccounts", "debitAccounts")); - private final String resourceNameForPermission = "GLACCOUNT"; + private static final String RESOURCE_NAME_FOR_PERMISSION = "GLACCOUNT"; private final PlatformSecurityContext context; private final GLAccountReadPlatformService glAccountReadPlatformService; @@ -111,7 +111,7 @@ public class GLAccountsApiResource { public String retrieveNewAccountDetails(@Context final UriInfo uriInfo, @QueryParam("type") @Parameter(description = "type") final Integer type) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); GLAccountData glAccountData = this.glAccountReadPlatformService.retrieveNewGLAccountDetails(type); glAccountData = handleTemplate(glAccountData); @@ -137,7 +137,7 @@ public String retrieveAllAccounts(@Context final UriInfo uriInfo, @QueryParam("disabled") @Parameter(description = "disabled") final Boolean disabled, @QueryParam("fetchRunningBalance") @Parameter(description = "fetchRunningBalance") final boolean runningBalance) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(false, runningBalance); final List glAccountDatas = this.glAccountReadPlatformService.retrieveAllGLAccounts(type, searchParam, usage, manualEntriesAllowed, disabled, associationParametersData); @@ -159,7 +159,7 @@ public String retreiveAccount(@PathParam("glAccountId") @Parameter(description = @Context final UriInfo uriInfo, @QueryParam("fetchRunningBalance") @Parameter(description = "fetchRunningBalance") final boolean runningBalance) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(false, runningBalance); diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java index 4f86f97480c..79814b25b6d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountType.java @@ -18,8 +18,6 @@ */ package org.apache.fineract.accounting.glaccount.domain; -import java.util.HashMap; -import java.util.Map; import org.apache.fineract.infrastructure.core.data.EnumOptionData; public enum GLAccountType { @@ -43,7 +41,6 @@ public String getCode() { return this.code; } - private static final Map intToEnumMap = new HashMap<>(); private static int minValue; private static int maxValue; @@ -53,7 +50,6 @@ public String getCode() { if (i == 0) { minValue = type.value; } - intToEnumMap.put(type.value, type); if (minValue >= type.value) { minValue = type.value; } @@ -86,9 +82,25 @@ public static EnumOptionData fromString(String accountType) { } } - public static GLAccountType fromInt(final int i) { - final GLAccountType type = intToEnumMap.get(Integer.valueOf(i)); - return type; + public static GLAccountType fromInt(final Integer v) { + if (v == null) { + return null; + } + + switch (v) { + case 1: + return ASSET; + case 2: + return LIABILITY; + case 3: + return EQUITY; + case 4: + return INCOME; + case 5: + return EXPENSE; + default: + return null; + } } public static int getMinValue() { diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java index 23933693d77..cc672fc7198 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java @@ -170,8 +170,8 @@ public CommandProcessingResult updateGLAccount(final Long glAccountId, final Jso private void validateForAttachedProduct(Long glAccountId) { String sql = "select count(*) from acc_product_mapping acc where acc.gl_account_id = ?"; try { - int count = this.jdbcTemplate.queryForObject(sql, Integer.class, glAccountId); - if (count > 0) { + Integer count = this.jdbcTemplate.queryForObject(sql, Integer.class, glAccountId); + if (count == null || count > 0) { throw new GLAccountDisableException(); } } catch (EmptyResultDataAccessException e) { diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java b/fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java index 52f03220a4f..80b9b49af4b 100755 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryType.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.accounting.journalentry.domain; -import java.util.HashMap; -import java.util.Map; - public enum JournalEntryType { CREDIT(1, "journalEntryType.credit"), DEBIT(2, "journalEntrytType.debit"); @@ -41,16 +38,19 @@ public String getCode() { return this.code; } - private static final Map intToEnumMap = new HashMap<>(); - - static { - for (final JournalEntryType type : JournalEntryType.values()) { - intToEnumMap.put(type.value, type); + public static JournalEntryType fromInt(final Integer v) { + if (v == null) { + return null; } - } - public static JournalEntryType fromInt(final int i) { - return intToEnumMap.get(i); + switch (v) { + case 1: + return CREDIT; + case 2: + return DEBIT; + default: + return null; + } } @Override @@ -59,11 +59,11 @@ public String toString() { } public boolean isDebitType() { - return this.value.equals(JournalEntryType.DEBIT.getValue()); + return this.equals(DEBIT); } public boolean isCreditType() { - return this.value.equals(JournalEntryType.CREDIT.getValue()); + return this.equals(CREDIT); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java b/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java index d233e73f26f..0826a4c049b 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/PortfolioProductType.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.accounting.producttoaccountmapping.domain; -import java.util.HashMap; -import java.util.Map; - public enum PortfolioProductType { LOAN(1, "productType.loan"), SAVING(2, "productType.saving"), CLIENT(5, "productType.client"), PROVISIONING(3, @@ -47,33 +44,41 @@ public String getCode() { return this.code; } - private static final Map intToEnumMap = new HashMap<>(); - - static { - for (final PortfolioProductType type : PortfolioProductType.values()) { - intToEnumMap.put(type.value, type); + public static PortfolioProductType fromInt(final Integer v) { + if (v == null) { + return null; } - } - public static PortfolioProductType fromInt(final int i) { - final PortfolioProductType type = intToEnumMap.get(Integer.valueOf(i)); - return type; + switch (v) { + case 1: + return LOAN; + case 2: + return SAVING; + case 3: + return CLIENT; + case 4: + return PROVISIONING; + case 5: + return SHARES; + default: + return null; + } } public boolean isSavingProduct() { - return this.value.equals(PortfolioProductType.SAVING.getValue()); + return this.equals(SAVING); } public boolean isLoanProduct() { - return this.value.equals(PortfolioProductType.LOAN.getValue()); + return this.equals(LOAN); } public boolean isClient() { - return this.value.equals(PortfolioProductType.CLIENT.getValue()); + return this.equals(CLIENT); } public boolean isShareProduct() { - return this.value.equals(PortfolioProductType.SHARES.getValue()); + return this.equals(SHARES); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java index 3ae49657e94..0e895bb9d04 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan; @@ -99,7 +100,7 @@ public void mergeProductToAccountMappingChanges(final JsonElement element, final throw new ProductToGLAccountMappingNotFoundException(portfolioProductType, productId, accountTypeName); } } else { - if (!accountMapping.getGlAccount().getId().equals(accountId)) { + if (accountMapping.getGlAccount() != null && !Objects.equals(accountMapping.getGlAccount().getId(), accountId)) { final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); changes.put(paramName, accountId); accountMapping.setGlAccount(glAccount); @@ -124,7 +125,7 @@ public void createOrmergeProductToAccountMappingChanges(final JsonElement elemen ProductToGLAccountMapping newAccountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount) .setProductId(productId).setProductType(portfolioProductType.getValue()).setFinancialAccountType(accountTypeId); this.accountMappingRepository.saveAndFlush(newAccountMapping); - } else if (!accountMapping.getGlAccount().getId().equals(accountId)) { + } else if (accountMapping.getGlAccount() != null && !Objects.equals(accountMapping.getGlAccount().getId(), accountId)) { final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); changes.put(paramName, accountId); accountMapping.setGlAccount(glAccount); @@ -272,14 +273,11 @@ public void updateChargeToIncomeAccountMappings(final JsonCommand command, final this.accountMappingRepository.delete(chargeToIncomeAccountMapping); } } - // create new mappings - final Set incomingCharges = inputChargeToIncomeAccountMap.keySet(); - incomingCharges.removeAll(existingCharges); - // incomingPaymentTypes now only contains the newly added - // payment Type mappings - for (final Long newCharge : incomingCharges) { - final Long newGLAccountId = inputChargeToIncomeAccountMap.get(newCharge); - saveChargeToFundSourceMapping(productId, newCharge, newGLAccountId, portfolioProductType, isPenalty); + + // only the newly added + for (Map.Entry entry : inputChargeToIncomeAccountMap.entrySet().stream() + .filter(e -> !existingCharges.contains(e.getKey())).toList()) { + saveChargeToFundSourceMapping(productId, entry.getKey(), entry.getValue(), portfolioProductType, isPenalty); } } } @@ -321,7 +319,7 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, } // If input map is empty, delete all existing mappings - if (inputPaymentChannelFundSourceMap.size() == 0) { + if (inputPaymentChannelFundSourceMap.isEmpty()) { this.accountMappingRepository.deleteAllInBatch(existingPaymentChannelToFundSourceMappings); } /** * Else,
@@ -348,14 +346,11 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, this.accountMappingRepository.delete(existingPaymentChannelToFundSourceMapping); } } - // create new mappings - final Set incomingPaymentTypes = inputPaymentChannelFundSourceMap.keySet(); - incomingPaymentTypes.removeAll(existingPaymentTypes); - // incomingPaymentTypes now only contains the newly added - // payment Type mappings - for (final Long newPaymentType : incomingPaymentTypes) { - final Long newGLAccountId = inputPaymentChannelFundSourceMap.get(newPaymentType); - savePaymentChannelToFundSourceMapping(productId, newPaymentType, newGLAccountId, portfolioProductType); + + // only the newly added + for (Map.Entry entry : inputPaymentChannelFundSourceMap.entrySet().stream() + .filter(e -> !existingPaymentTypes.contains(e.getKey())).toList()) { + savePaymentChannelToFundSourceMapping(productId, entry.getKey(), entry.getValue(), portfolioProductType); } } } diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java b/fineract-core/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java index 6eb18988097..d8cb1b81bb7 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java @@ -37,7 +37,6 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.UriInfo; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -112,7 +111,7 @@ public String handleBatchRequests( validateRequestMethodsAllowedOnInstanceType(requestList); // Gets back the consolidated BatchResponse from BatchApiservice - List result = new ArrayList<>(); + List result; // If the request is to be handled as a Transaction. All requests will // be rolled back on error diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java index a7d799adab0..f3104b54345 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java @@ -23,6 +23,7 @@ import com.google.gson.Gson; import com.jayway.jsonpath.JsonPathException; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.github.resilience4j.core.functions.Either; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -51,7 +52,6 @@ import org.apache.fineract.batch.exception.ErrorInfo; import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode; import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; -import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.infrastructure.core.filters.BatchCallHandler; import org.apache.fineract.infrastructure.core.filters.BatchFilter; @@ -61,10 +61,8 @@ import org.springframework.dao.NonTransientDataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionExecution; -import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.TransactionTemplate; @@ -103,8 +101,7 @@ public class BatchApiServiceImpl implements BatchApiService { */ @Override public List handleBatchRequestsWithoutEnclosingTransaction(final List requestList, UriInfo uriInfo) { - BatchRequestContextHolder.setEnclosingTransaction(Optional.empty()); - return handleBatchRequests(false, requestList, uriInfo); + return handleBatchRequests(requestList, uriInfo, false); } /** @@ -116,7 +113,18 @@ public List handleBatchRequestsWithoutEnclosingTransaction(final */ @Override public List handleBatchRequestsWithEnclosingTransaction(final List requestList, final UriInfo uriInfo) { - return callInTransaction(Function.identity()::apply, () -> handleBatchRequests(true, requestList, uriInfo)); + return handleBatchRequests(requestList, uriInfo, true); + } + + private List handleBatchRequests(final List requestList, final UriInfo uriInfo, + boolean enclosingTransaction) { + BatchRequestContextHolder.setIsEnclosingTransaction(enclosingTransaction); + try { + return enclosingTransaction ? callInTransaction(Function.identity()::apply, () -> handleRequestNodes(requestList, uriInfo)) + : handleRequestNodes(requestList, uriInfo); + } finally { + BatchRequestContextHolder.resetIsEnclosingTransaction(); + } } /** @@ -135,18 +143,15 @@ private List callInTransaction(Consumer tran TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionConfigurator.accept(transactionTemplate); return transactionTemplate.execute(status -> { - BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status)); + BatchRequestContextHolder.setEnclosingTransaction(status); try { responseList.addAll(request.get()); return responseList; } catch (BatchExecutionException ex) { - status.setRollbackOnly(); - return List.of(buildErrorResponse(ex.getCause(), ex.getRequest())); - } catch (RuntimeException ex) { - status.setRollbackOnly(); - return buildErrorResponses(ex, responseList); + responseList.add(buildErrorResponse(ex.getCause(), ex.getRequest())); + return responseList; } finally { - BatchRequestContextHolder.setEnclosingTransaction(Optional.empty()); + BatchRequestContextHolder.resetTransaction(); } }); } catch (TransactionException | NonTransientDataAccessException ex) { @@ -162,24 +167,17 @@ private List callInTransaction(Consumer tran * @param uriInfo * @return {@code List} */ - private List handleBatchRequests(boolean enclosingTransaction, final List requestList, - final UriInfo uriInfo) { + private List handleRequestNodes(final List requestList, final UriInfo uriInfo) { final List rootNodes; try { rootNodes = this.resolutionHelper.buildNodesTree(requestList); } catch (BatchReferenceInvalidException e) { - return List.of(buildErrorResponse(e)); + return List.of(buildOrThrowErrorResponse(e, null)); } final ArrayList responseList = new ArrayList<>(requestList.size()); for (BatchRequestNode rootNode : rootNodes) { - if (enclosingTransaction) { - this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo, enclosingTransaction); - } else { - ArrayList localResponseList = new ArrayList<>(); - this.callRequestRecursive(rootNode.getRequest(), rootNode, localResponseList, uriInfo, enclosingTransaction); - responseList.addAll(localResponseList); - } + this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo); } responseList.sort(Comparator.comparing(BatchResponse::getRequestId)); return responseList; @@ -196,18 +194,10 @@ private List handleBatchRequests(boolean enclosingTransaction, fi * the collected responses * @return {@code BatchResponse} */ - private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List responseList, UriInfo uriInfo, - boolean enclosingTransaction) { + private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List responseList, + UriInfo uriInfo) { // run current node - BatchResponse response; - if (enclosingTransaction) { - response = executeRequest(request, uriInfo); - } else { - List transactionResponse = callInTransaction( - transactionTemplate -> transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW), - () -> List.of(executeRequest(request, uriInfo))); - response = transactionResponse.get(0); - } + BatchResponse response = executeRequest(request, uriInfo); responseList.add(response); if (response.getStatusCode() != null && response.getStatusCode() == SC_OK) { // run child nodes @@ -216,13 +206,10 @@ private void callRequestRecursive(BatchRequest request, BatchRequestNode request BatchRequest resolvedChildRequest; try { resolvedChildRequest = this.resolutionHelper.resolveRequest(childRequest, response); + callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo); } catch (JsonPathException jpex) { - responseList.add(buildErrorResponse(jpex, childRequest)); - return; - } catch (RuntimeException ex) { - throw new BatchExecutionException(childRequest, ex); + responseList.add(buildOrThrowErrorResponse(jpex, childRequest)); } - callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo, enclosingTransaction); }); } else { responseList.addAll(parentRequestFailedRecursive(request, requestNode, response, null)); @@ -244,7 +231,7 @@ private BatchResponse executeRequest(BatchRequest request, UriInfo uriInfo) { log.debug("Batch request: method [{}], relative url [{}]", request.getMethod(), request.getRelativeUrl()); Either preprocessorResult = runPreprocessors(request); if (preprocessorResult.isLeft()) { - throw new BatchExecutionException(request, preprocessorResult.getLeft()); + return buildOrThrowErrorResponse(preprocessorResult.getLeft(), request); } else { request = preprocessorResult.get(); } @@ -252,19 +239,16 @@ private BatchResponse executeRequest(BatchRequest request, UriInfo uriInfo) { BatchRequestContextHolder.setRequestAttributes(new HashMap<>(Optional.ofNullable(request.getHeaders()) .map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue))) .orElse(Collections.emptyMap()))); - BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute); - Optional transaction = BatchRequestContextHolder.getEnclosingTransaction(); - if (transaction.isPresent()) { + if (BatchRequestContextHolder.isEnclosingTransaction()) { entityManager.flush(); } + BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute); final BatchResponse rootResponse = callHandler.serviceCall(request, uriInfo); log.debug("Batch response: status code [{}], method [{}], relative url [{}]", rootResponse.getStatusCode(), request.getMethod(), request.getRelativeUrl()); return rootResponse; - } catch (AbstractIdempotentCommandException idempotentException) { - return buildErrorResponse(idempotentException, request); } catch (RuntimeException ex) { - throw new BatchExecutionException(request, ex); + return buildOrThrowErrorResponse(ex, request); } finally { BatchRequestContextHolder.resetRequestAttributes(); } @@ -312,11 +296,6 @@ private List parentRequestFailedRecursive(@NotNull BatchRequest r return responseList; } - @NotNull - private BatchResponse buildErrorResponse(@NotNull Throwable ex) { - return buildErrorResponse(ex, null); - } - /** * Return the response when any exception raised * @@ -345,6 +324,15 @@ private BatchResponse buildErrorResponse(Throwable ex, BatchRequest request) { return buildErrorResponse(requestId, statusCode, body, headers); } + private BatchResponse buildOrThrowErrorResponse(RuntimeException ex, BatchRequest request) { + BatchResponse response = buildErrorResponse(ex, request); + if (response.getStatusCode() != SC_OK && BatchRequestContextHolder.isEnclosingTransaction()) { + BatchRequestContextHolder.getTransaction().ifPresent(TransactionExecution::setRollbackOnly); + throw new BatchExecutionException(request, ex); + } + return response; + } + @NotNull private List buildErrorResponses(Throwable ex, @NotNull List responseList) { BatchResponse response = responseList.isEmpty() ? null @@ -367,9 +355,10 @@ private List buildErrorResponses(Throwable ex, @NotNull List buildErrorResponses(Throwable ex, @NotNull List headers) { return new BatchResponse().setRequestId(requestId).setStatusCode(statusCode == null ? SC_INTERNAL_SERVER_ERROR : statusCode) .setBody(body == null ? "Request with id " + requestId + " was erroneous!" : body).setHeaders(headers); diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index c56a7c02bf7..a035daedaa3 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -202,6 +202,14 @@ public void setCommandJson(final String json) { this.commandAsJson = json; } + public AppUser getMaker() { + return maker; + } + + public AppUser getChecker() { + return checker; + } + public String getActionName() { return this.actionName; } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionAsCommandIsNotApprovedByCheckerException.java b/fineract-core/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionAsCommandIsNotApprovedByCheckerException.java deleted file mode 100644 index b7172efeecc..00000000000 --- a/fineract-core/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionAsCommandIsNotApprovedByCheckerException.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * 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. - */ -package org.apache.fineract.commands.exception; - -import org.apache.fineract.commands.domain.CommandSource; - -public class RollbackTransactionAsCommandIsNotApprovedByCheckerException extends RuntimeException { - - /** - * When maker-checker is configured globally and also for the current transaction. - * - * An initial save determines if there are any integrity rule or data problems. - * - * If there isn't... and the transaction is from a maker... then this roll back is issued and the - * commandSourceResult is used to write the audit entry. - */ - private final CommandSource commandSourceResult; - - public RollbackTransactionAsCommandIsNotApprovedByCheckerException(final CommandSource commandSourceResult) { - this.commandSourceResult = commandSourceResult; - } - - public CommandSource getCommandSourceResult() { - return this.commandSourceResult; - } -} diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionNotApprovedException.java b/fineract-core/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionNotApprovedException.java new file mode 100644 index 00000000000..60d727bb336 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/commands/exception/RollbackTransactionNotApprovedException.java @@ -0,0 +1,41 @@ +/** + * 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. + */ +package org.apache.fineract.commands.exception; + +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; + +/** + * When maker-checker is configured globally and also for the current transaction. An initial save determines if there + * are any integrity rule or data problems. If there isn't... and the transaction is from a maker... then this roll back + * is issued and the commandSourceResult is used to write the audit entry. + */ +public class RollbackTransactionNotApprovedException extends RuntimeException { + + private final CommandProcessingResult result; + + public RollbackTransactionNotApprovedException(Long commandId, Long entityId) { + this.result = new CommandProcessingResultBuilder().withCommandId(commandId).withEntityId(entityId).setRollbackTransaction(true) + .build(); + } + + public CommandProcessingResult getResult() { + return result; + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.java b/fineract-core/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.java index e4b3d49722c..f3848e77ffc 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/exception/UnsupportedCommandException.java @@ -30,6 +30,11 @@ public UnsupportedCommandException(final String unsupportedCommandName) { this.unsupportedCommandName = unsupportedCommandName; } + public UnsupportedCommandException(final String unsupportedCommandName, String message) { + super(message); + this.unsupportedCommandName = unsupportedCommandName; + } + public String getUnsupportedCommandName() { return this.unsupportedCommandName; } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java b/fineract-core/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java index a4297f9d52f..38ca3e705f7 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/provider/CommandHandlerProvider.java @@ -65,7 +65,11 @@ private void initializeHandlerRegistry() { log.debug("Register command handler '{}' ...", commandHandlerName); final CommandType commandType = applicationContext.findAnnotationOnBean(commandHandlerName, CommandType.class); try { - registeredHandlers.put(commandType.entity() + "|" + commandType.action(), commandHandlerName); + if (commandType != null) { + registeredHandlers.put(commandType.entity() + "|" + commandType.action(), commandHandlerName); + } else { + log.error("Unable to register command handler '{}'!", commandHandlerName); + } } catch (final Throwable th) { log.error("Unable to register command handler '{}'!", commandHandlerName, th); } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.java index d566c622244..49ed72c787e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandProcessingService.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.commands.service; -import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -28,8 +27,6 @@ public interface CommandProcessingService { CommandProcessingResult executeCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker); - CommandProcessingResult logCommand(CommandSource commandSourceResult); - - boolean validateCommand(CommandWrapper commandWrapper, AppUser user); + boolean validateRollbackCommand(CommandWrapper commandWrapper, AppUser user); } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java index 193041eafd9..cb5dfaca0c0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java @@ -26,7 +26,10 @@ import org.apache.fineract.commands.domain.CommandSourceRepository; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.exception.CommandNotFoundException; +import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.useradministration.domain.AppUser; import org.jetbrains.annotations.NotNull; @@ -95,19 +98,24 @@ public CommandSource findCommandSource(CommandWrapper wrapper, String idempotenc idempotencyKey); } - @Transactional(propagation = Propagation.REQUIRED) public CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { - CommandSource commandSourceResult; - if (jsonCommand.commandId() != null) { - commandSourceResult = commandSourceRepository.findById(jsonCommand.commandId()) - .orElseThrow(() -> new CommandNotFoundException(jsonCommand.commandId())); - commandSourceResult.markAsChecked(maker); - } else { - commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey, UNDER_PROCESSING.getValue()); - } + CommandSource commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey, + UNDER_PROCESSING.getValue()); if (commandSourceResult.getCommandJson() == null) { commandSourceResult.setCommandJson("{}"); } return commandSourceResult; } + + @Transactional + public CommandProcessingResult processCommand(NewCommandSourceHandler handler, JsonCommand command, CommandSource commandSource, + AppUser user, boolean isApprovedByChecker, boolean isMakerChecker) { + final CommandProcessingResult result = handler.processCommand(command); + boolean isRollback = !isApprovedByChecker && !user.isCheckerSuperUser() && (isMakerChecker || result.isRollbackTransaction()); + if (isRollback) { + commandSource.markAsAwaitingApproval(); + throw new RollbackTransactionNotApprovedException(commandSource.getId(), commandSource.getResourceId()); + } + return result; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 040ce1d1c37..4fcb56026cf 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.commands.service; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants; import org.apache.fineract.portfolio.client.api.ClientApiConstants; @@ -47,6 +48,7 @@ public class CommandWrapperBuilder { private String jobName; private String idempotencyKey; + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "TODO: fix this!") public CommandWrapper build() { return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId, @@ -2571,6 +2573,15 @@ public CommandWrapperBuilder recurringDepositAccountActivation(final Long accoun return this; } + public CommandWrapperBuilder recurringDepositAccountUndoActivation(final Long accountId) { + this.actionName = "UNDO_ACTIVATE"; + this.entityName = "RECURRINGDEPOSITACCOUNT"; + this.savingsId = accountId; + this.entityId = accountId; + this.href = "/recurringdepositaccounts/" + accountId + "?command=undoactivate"; + return this; + } + public CommandWrapperBuilder closeRecurringDepositAccount(final Long accountId) { this.actionName = "CLOSE"; this.entityName = "RECURRINGDEPOSITACCOUNT"; diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index 225c25f88a1..fcd17c3f679 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -19,6 +19,7 @@ package org.apache.fineract.commands.service; import com.google.gson.JsonElement; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.commands.domain.CommandSource; @@ -26,6 +27,8 @@ import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.exception.CommandNotAwaitingApprovalException; import org.apache.fineract.commands.exception.CommandNotFoundException; +import org.apache.fineract.commands.exception.UnsupportedCommandException; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; @@ -45,10 +48,10 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio private final FromJsonHelper fromApiJsonHelper; private final CommandProcessingService processAndLogCommandService; private final SchedulerJobRunnerReadService schedulerJobRunnerReadService; + private final ConfigurationDomainService configurationService; @Override public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { - boolean isApprovedByChecker = false; // check if is update of own account details @@ -77,7 +80,6 @@ public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { @Override public CommandProcessingResult approveEntry(final Long makerCheckerId) { - final CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId); validateIsUpdateAllowed(); @@ -111,16 +113,24 @@ public Long deleteEntry(final Long makerCheckerId) { } private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) { - - final CommandSource commandSourceInput = this.commandSourceRepository.findById(makerCheckerId) + final CommandSource commandSource = this.commandSourceRepository.findById(makerCheckerId) .orElseThrow(() -> new CommandNotFoundException(makerCheckerId)); - if (!commandSourceInput.isMarkedAsAwaitingApproval()) { + if (!commandSource.isMarkedAsAwaitingApproval()) { throw new CommandNotAwaitingApprovalException(makerCheckerId); } - - this.context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode()); - - return commandSourceInput; + AppUser appUser = this.context.authenticatedUser(); + String permissionCode = commandSource.getPermissionCode(); + appUser.validateHasCheckerPermissionTo(permissionCode); + if (!configurationService.isSameMakerCheckerEnabled() && !appUser.isCheckerSuperUser()) { + AppUser maker = commandSource.getMaker(); + if (maker == null) { + throw new UnsupportedCommandException(permissionCode, "Maker user is missing."); + } + if (Objects.equals(appUser.getId(), maker.getId())) { + throw new UnsupportedCommandException(permissionCode, "Can not be checked by the same user."); + } + } + return commandSource; } private void validateIsUpdateAllowed() { diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java index b0ac361718c..6b3a6ae8839 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java @@ -35,14 +35,12 @@ import org.apache.fineract.commands.domain.CommandProcessingResultType; import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandWrapper; -import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException; import org.apache.fineract.commands.exception.UnsupportedCommandException; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.provider.CommandHandlerProvider; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; @@ -58,7 +56,6 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @Slf4j @@ -76,9 +73,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer private final ConfigurationDomainService configurationDomainService; private final CommandHandlerProvider commandHandlerProvider; private final IdempotencyKeyResolver idempotencyKeyResolver; - private final IdempotencyKeyGenerator idempotencyKeyGenerator; private final CommandSourceService commandSourceService; - private final ErrorHandler errorHandler; private final FineractRequestContextHolder fineractRequestContextHolder; private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); @@ -92,73 +87,68 @@ public CommandProcessingResult executeCommand(final CommandWrapper wrapper, fina Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null); boolean isRetry = commandId != null; + boolean isEnclosingTransaction = BatchRequestContextHolder.isEnclosingTransaction(); CommandSource commandSource = null; String idempotencyKey; if (isRetry) { commandSource = commandSourceService.getCommandSource(commandId); idempotencyKey = commandSource.getIdempotencyKey(); + } else if ((commandId = command.commandId()) != null) { // action on the command itself + commandSource = commandSourceService.getCommandSource(commandId); + idempotencyKey = commandSource.getIdempotencyKey(); } else { idempotencyKey = idempotencyKeyResolver.resolve(wrapper); } exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey, isRetry); - boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent(); + AppUser user = context.authenticatedUser(wrapper); if (commandSource == null) { - AppUser user = context.authenticatedUser(wrapper); - if (sameTransaction) { + if (isEnclosingTransaction) { commandSource = commandSourceService.getInitialCommandSource(wrapper, command, user, idempotencyKey); } else { commandSource = commandSourceService.saveInitialNewTransaction(wrapper, command, user, idempotencyKey); - storeCommandIdInContext(commandSource); // Store command id as a request attribute + commandId = commandSource.getId(); } } + if (commandId != null) { + storeCommandIdInContext(commandSource); // Store command id as a request attribute + } + + boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName()); + if (isApprovedByChecker || (isMakerChecker && user.isCheckerSuperUser())) { + commandSource.markAsChecked(user); + } setIdempotencyKeyStoreFlag(true); final CommandProcessingResult result; try { - result = findCommandHandler(wrapper).processCommand(command); + result = commandSourceService.processCommand(findCommandHandler(wrapper), command, commandSource, user, isApprovedByChecker, + isMakerChecker); } catch (Throwable t) { // NOSONAR RuntimeException mappable = ErrorHandler.getMappable(t); ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable); - commandSource.setResultStatusCode(errorInfo.getStatusCode()); + Integer statusCode = errorInfo.getStatusCode(); + commandSource.setResultStatusCode(statusCode); commandSource.setResult(errorInfo.getMessage()); - commandSource.setStatus(ERROR); - if (!sameTransaction) { // TODO: temporary solution + if (statusCode != SC_OK) { + commandSource.setStatus(ERROR); + } + if (!isEnclosingTransaction) { // TODO: temporary solution commandSource = commandSourceService.saveResultNewTransaction(commandSource); } - publishHookErrorEvent(wrapper, command, errorInfo); // TODO must be performed in a new transaction + // must not throw any exception; must persist in new transaction as the current transaction was already + // marked as rollback + publishHookErrorEvent(wrapper, command, errorInfo); throw mappable; } - commandSource.updateForAudit(result); commandSource.setResultStatusCode(SC_OK); + commandSource.updateForAudit(result); commandSource.setResult(toApiJsonSerializer.serializeResult(result)); commandSource.setStatus(PROCESSED); - - boolean isRollback = !isApprovedByChecker && (result.isRollbackTransaction() - || configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName())); - // TODO: this should be removed, can not override audit information (and maker-checker does not work) - if (!isRollback && result.hasChanges()) { - commandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges())); - } - commandSource = commandSourceService.saveResultSameTransaction(commandSource); - if (sameTransaction) { - storeCommandIdInContext(commandSource); // Store command id as a request attribute - } - - if (isRollback) { - /* - * JournalEntry will generate a new transactionId every time. Updating the transactionId with old - * transactionId, because as there are no entries are created with new transactionId, will throw an error - * when checker approves the transaction - */ - commandSource.setTransactionId(command.getTransactionId()); - // TODO: this should be removed together with lines 147-149 - commandSource.setCommandJson(command.json()); // Set back CommandSource json data - throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSource); - } + storeCommandIdInContext(commandSource); // Store command id as a request attribute result.setRollbackTransaction(null); publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, result); // TODO must be performed in a @@ -201,24 +191,8 @@ private void setIdempotencyKeyStoreFlag(boolean flag) { fineractRequestContextHolder.setAttribute(IDEMPOTENCY_KEY_STORE_FLAG, flag); } - @Transactional - @Override - public CommandProcessingResult logCommand(CommandSource commandSource) { - commandSource.markAsAwaitingApproval(); - if (commandSource.getIdempotencyKey() == null) { - commandSource.setIdempotencyKey(idempotencyKeyGenerator.create()); - } - commandSource = commandSourceService.saveResultSameTransaction(commandSource); - - return new CommandProcessingResultBuilder().withCommandId(commandSource.getId()).withEntityId(commandSource.getResourceId()) - .build(); - } - @SuppressWarnings("unused") public CommandProcessingResult fallbackExecuteCommand(Exception e) { - if (e instanceof RollbackTransactionAsCommandIsNotApprovedByCheckerException ex) { - return logCommand(ex.getCommandSourceResult()); - } throw ErrorHandler.getMappable(e); } @@ -281,10 +255,10 @@ private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) } @Override - public boolean validateCommand(final CommandWrapper commandWrapper, final AppUser user) { - boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName()); + public boolean validateRollbackCommand(final CommandWrapper commandWrapper, final AppUser user) { user.validateHasPermissionTo(commandWrapper.getTaskPermissionName()); - return rollbackTransaction; + boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName()); + return isMakerChecker && !user.isCheckerSuperUser(); } private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java index a15ae0130b2..f2d3b4e9741 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java @@ -18,10 +18,12 @@ */ package org.apache.fineract.infrastructure.cache.service; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.cache.CacheApiConstants; @@ -105,7 +107,7 @@ public Map switchToCache(final boolean ehcacheEnabled, final Cac } currentCacheManager = ehCacheManager; - if (currentCacheManager.getCacheNames().size() == 0) { + if (currentCacheManager.getCacheNames().isEmpty()) { log.error("No caches configured for activated CacheManager {}", currentCacheManager); } } @@ -115,10 +117,17 @@ public Map switchToCache(final boolean ehcacheEnabled, final Cac return changes; } + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "TODO: fix this!") private void clearEhCache() { Iterable cacheNames = ehCacheManager.getCacheNames(); for (String cacheName : cacheNames) { - ehCacheManager.getCache(cacheName).clear(); + try { + if (Objects.nonNull(ehCacheManager.getCache(cacheName))) { + Objects.requireNonNull(ehCacheManager.getCache(cacheName)).clear(); + } + } catch (NullPointerException npe) { + log.warn(npe.getMessage()); + } } } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index 06969ef9a7a..d2bf2c2b94f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -25,6 +25,8 @@ public interface ConfigurationDomainService { boolean isMakerCheckerEnabledForTask(String taskPermissionCode); + boolean isSameMakerCheckerEnabled(); + boolean isAmazonS3Enabled(); boolean isRescheduleFutureRepaymentsEnabled(); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 5f80f69cd73..0e0b57de83f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -38,6 +38,8 @@ public class FineractProperties { private String idempotencyKeyHeaderName; + private Boolean insecureHttpClient; + private FineractTenantProperties tenant; private FineractModeProperties mode; diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java index f744f168687..b71d498266a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java @@ -23,7 +23,6 @@ import static org.apache.http.HttpStatus.SC_CONFLICT; import static org.apache.http.HttpStatus.SC_FORBIDDEN; import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; -import static org.apache.http.HttpStatus.SC_LOCKED; import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED; import static org.apache.http.HttpStatus.SC_NOT_FOUND; import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE; @@ -107,7 +106,7 @@ public static ApiGlobalErrorResponse loanIsLocked(final Long loanId) { return create(SC_CONFLICT, "error.msg.loan.locked", msg, msg); } - public static ApiGlobalErrorResponse locked(String type, String identifier) { + public static ApiGlobalErrorResponse conflict(String type, String identifier) { String details = ""; if (type == null) { type = "unknown"; @@ -119,7 +118,7 @@ public static ApiGlobalErrorResponse locked(String type, String identifier) { } String msg = "The server is currently unable to handle the request due to concurrent modification " + details + ", please try again"; - return create(SC_LOCKED, "error.msg.platform.service." + type + ".conflict", msg, msg); + return create(SC_CONFLICT, "error.msg.platform.service." + type + ".conflict", msg, msg); } public static ApiGlobalErrorResponse unAuthorized(final String defaultUserMessage) { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java index 3776fb3de37..59202329358 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java @@ -27,15 +27,16 @@ public final class BatchRequestContextHolder { private BatchRequestContextHolder() {} - private static final ThreadLocal> batchAttributes = new NamedThreadLocal<>("BatchAttributesForProcessing"); + private static final ThreadLocal> batchAttributes = new NamedThreadLocal<>("batchAttributes"); - private static final ThreadLocal> enclosingTransaction = new NamedThreadLocal<>("EnclosingTransaction") { + private static final ThreadLocal> batchTransaction = new NamedThreadLocal<>("batchTransaction") { @Override protected Optional initialValue() { return Optional.empty(); } }; + private static final ThreadLocal isEnclosingTransaction = new NamedThreadLocal<>("isEnclosingTransaction"); /** * True if the batch attributes are set @@ -47,12 +48,12 @@ public static boolean isBatchRequest() { } /** - * True if the batch attributes are set and the enclosing transaction is set to true + * Returns the batch attributes for the current thread. * - * @return + * @return the batch attributes for the current thread, cna be null */ - public static Optional getEnclosingTransaction() { - return enclosingTransaction.get(); + public static Map getRequestAttributes() { + return batchAttributes.get(); } /** @@ -66,27 +67,73 @@ public static void setRequestAttributes(Map requestAttributes) { } /** - * Returns the batch attributes for the current thread. + * Reset the batch attributes for the current thread. + */ + public static void resetRequestAttributes() { + batchAttributes.remove(); + } + + /** + * True if the batch attributes are set and the enclosing transaction is set to true * - * @return the batch attributes for the current thread, cna be null + * @return */ - public static Map getRequestAttributes() { - return batchAttributes.get(); + public static boolean isEnclosingTransaction() { + return Boolean.TRUE.equals(isEnclosingTransaction.get()); } /** - * Reset the batch attributes for the current thread. + * Set the isEnclosingTransaction flag for the current thread. + * + * @param isEnclosingTransaction */ - public static void resetRequestAttributes() { - batchAttributes.remove(); + public static void setIsEnclosingTransaction(boolean isEnclosingTransaction) { + BatchRequestContextHolder.isEnclosingTransaction.set(isEnclosingTransaction); + } + + public static void resetIsEnclosingTransaction() { + isEnclosingTransaction.remove(); + } + + /** + * Return the transaction + * + * @return + */ + public static Optional getTransaction() { + return batchTransaction.get(); + } + + /** + * Return the enclosing transaction + * + * @return + */ + public static Optional getEnclosingTransaction() { + return isEnclosingTransaction() ? getTransaction() : Optional.empty(); + } + + /** + * Set the transaction for the current thread. + * + * @param enclosingTransaction + */ + public static void setTransaction(TransactionStatus enclosingTransaction) { + batchTransaction.set(Optional.ofNullable(enclosingTransaction)); } /** - * Set the enclosing transaction flag for the current thread. + * Set the enclosing transaction for the current thread. * * @param enclosingTransaction */ - public static void setEnclosingTransaction(Optional enclosingTransaction) { - BatchRequestContextHolder.enclosingTransaction.set(enclosingTransaction); + public static void setEnclosingTransaction(TransactionStatus enclosingTransaction) { + if (isEnclosingTransaction()) { + setTransaction(enclosingTransaction); + } + } + + public static void resetTransaction() { + batchTransaction.set(Optional.empty()); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java index 0ea52b8fb04..bd0f969fd0d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/IdempotentCommandProcessFailedException.java @@ -39,6 +39,6 @@ public IdempotentCommandProcessFailedException(CommandWrapper wrapper, String id @NotNull public Integer getStatusCode() { // If the database inconsistent we return http 500 instead of null pointer exception - return statusCode == null ? SC_INTERNAL_SERVER_ERROR : statusCode; + return statusCode == null ? Integer.valueOf(SC_INTERNAL_SERVER_ERROR) : statusCode; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java index 902fb9e92d2..d3d8f9710a7 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConcurrencyFailureExceptionMapper.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.infrastructure.core.exceptionmapper; -import static org.apache.http.HttpStatus.SC_LOCKED; +import static org.apache.http.HttpStatus.SC_CONFLICT; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -53,8 +53,8 @@ public Response toResponse(final ConcurrencyFailureException exception) { type = "lock"; identifier = null; } - final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.locked(type, identifier); - return Response.status(SC_LOCKED).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); + final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.conflict(type, identifier); + return Response.status(SC_CONFLICT).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); } @Override diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java index 4facc0976d6..c37625041c0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/IdempotentCommandExceptionMapper.java @@ -19,10 +19,11 @@ package org.apache.fineract.infrastructure.core.exceptionmapper; import static org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER; +import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.apache.http.HttpStatus.SC_OK; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import lombok.extern.slf4j.Slf4j; @@ -46,18 +47,18 @@ public class IdempotentCommandExceptionMapper implements FineractExceptionMapper @Override public Response toResponse(final AbstractIdempotentCommandException exception) { log.warn("Processing {} request: {}", exception.getClass().getName(), exception.getMessage()); - Status status = null; + Integer status = null; if (exception instanceof IdempotentCommandProcessSucceedException pse) { Integer statusCode = pse.getStatusCode(); - status = statusCode == null ? Status.OK : Status.fromStatusCode(statusCode); + status = statusCode == null ? SC_OK : statusCode; } if (exception instanceof IdempotentCommandProcessUnderProcessingException) { - status = Status.CONFLICT; + status = 425; } else if (exception instanceof IdempotentCommandProcessFailedException pfe) { - status = Status.fromStatusCode(pfe.getStatusCode()); + status = pfe.getStatusCode(); } if (status == null) { - status = Status.INTERNAL_SERVER_ERROR; + status = SC_INTERNAL_SERVER_ERROR; } return Response.status(status).entity(exception.getResponse()).header(IDEMPOTENT_CACHE_HEADER, "true") .type(MediaType.APPLICATION_JSON).build(); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java index b4af9e3ff53..3e293d19bd8 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OptimisticLockExceptionMapper.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.infrastructure.core.exceptionmapper; -import static org.apache.http.HttpStatus.SC_LOCKED; +import static org.apache.http.HttpStatus.SC_CONFLICT; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -46,12 +46,12 @@ public Response toResponse(final OptimisticLockException exception) { log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage()); String type = exception.getQuery() == null ? "unknown" : "query"; String identifier = "unknown"; - final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.locked(type, identifier); - return Response.status(SC_LOCKED).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); + final ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.conflict(type, identifier); + return Response.status(SC_CONFLICT).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); } @Override public int errorCode() { - return 4009; + return 4019; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/RollbackTransactionNotApprovedExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/RollbackTransactionNotApprovedExceptionMapper.java new file mode 100644 index 00000000000..dfa2d3da862 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/RollbackTransactionNotApprovedExceptionMapper.java @@ -0,0 +1,56 @@ +/** + * 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. + */ +package org.apache.fineract.infrastructure.core.exceptionmapper; + +import com.google.gson.Gson; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * An {@link ExceptionMapper} to map {@link PlatformApiDataValidationException} thrown by platform into a HTTP API + * friendly format. + * + * The {@link PlatformApiDataValidationException} is typically thrown in data validation of the parameters passed in + * with an api request. + */ +@Provider +@Component +@Scope("singleton") +@Slf4j +public class RollbackTransactionNotApprovedExceptionMapper + implements FineractExceptionMapper, ExceptionMapper { + + @Override + public Response toResponse(final RollbackTransactionNotApprovedException exception) { + log.warn("Exception: {}", exception.getClass().getName()); + return Response.ok().entity(new Gson().toJson(exception.getResult())).type(MediaType.APPLICATION_JSON).build(); + } + + @Override + public int errorCode() { + return 2000; + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java index 9743e64d044..39021b96cc6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/UnsupportedCommandExceptionMapper.java @@ -47,8 +47,12 @@ public Response toResponse(final UnsupportedCommandException exception) { final List errors = new ArrayList<>(); final StringBuilder validationErrorCode = new StringBuilder("error.msg.command.unsupported"); + String message = exception.getMessage(); final StringBuilder defaultEnglishMessage = new StringBuilder("The command ").append(exception.getUnsupportedCommandName()) .append(" is not supported."); + if (message != null) { + defaultEnglishMessage.append(" ").append(message); + } log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultEnglishMessage); final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), exception.getUnsupportedCommandName(), exception.getUnsupportedCommandName()); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java index f2670fec4f0..c014ab73430 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java @@ -25,7 +25,6 @@ import org.apache.fineract.commands.domain.CommandSourceRepository; import org.apache.fineract.commands.service.CommandSourceService; import org.apache.fineract.commands.service.SynchronousCommandProcessingService; -import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.springframework.stereotype.Component; @@ -39,11 +38,9 @@ public class IdempotencyStoreHelper { public void storeCommandResult(Integer response, String body, Long commandId) { commandSourceRepository.findById(commandId).ifPresent(commandSource -> { - boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent(); commandSource.setResultStatusCode(response); commandSource.setResult(body); - commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource) - : commandSourceService.saveResultNewTransaction(commandSource); + commandSourceService.saveResultSameTransaction(commandSource); }); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java index 0074442ef71..a3cd2bd4100 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java @@ -736,6 +736,7 @@ public BigDecimal convertFrom(final String numericalValueFormatted, final String /*** * TODO: Vishwas move all Locale related code to a separate Utils class ***/ + @SuppressWarnings("StringSplitter") public static Locale localeFromString(final String localeAsString) { if (StringUtils.isBlank(localeAsString)) { @@ -754,16 +755,16 @@ public static Locale localeFromString(final String localeAsString) { final String[] localeParts = localeAsString.split("_"); - if (localeParts != null && localeParts.length == 1) { + if (localeParts.length == 1) { languageCode = localeParts[0]; } - if (localeParts != null && localeParts.length == 2) { + if (localeParts.length == 2) { languageCode = localeParts[0]; countryCode = localeParts[1]; } - if (localeParts != null && localeParts.length == 3) { + if (localeParts.length == 3) { languageCode = localeParts[0]; countryCode = localeParts[1]; variantCode = localeParts[2]; diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java index a7bf95d933d..76755e4b5b5 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/Page.java @@ -23,10 +23,10 @@ public class Page implements Serializable { - private final int totalFilteredRecords; + private final Integer totalFilteredRecords; private final List pageItems; - public Page(final List pageItems, final int totalFilteredRecords) { + public Page(final List pageItems, final Integer totalFilteredRecords) { this.pageItems = pageItems; this.totalFilteredRecords = totalFilteredRecords; } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedLocalRequest.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedLocalRequest.java index b2c64544230..5289850cc40 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedLocalRequest.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedLocalRequest.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.core.service; import java.util.Locale; +import java.util.Objects; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -38,4 +39,25 @@ public class PagedLocalRequest extends PagedRequest { public Locale getLocaleObject() { return locale == null ? null : JsonParserHelper.localeFromString(locale); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof PagedLocalRequest)) { + return false; + } + if (!super.equals(o)) { + return false; + } + PagedLocalRequest that = (PagedLocalRequest) o; + return Objects.equals(dateFormat, that.dateFormat) && Objects.equals(dateTimeFormat, that.dateTimeFormat) + && Objects.equals(locale, that.locale); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), dateFormat, dateTimeFormat, locale); + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedRequest.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedRequest.java index e2becbb50d5..b87e4d308a7 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedRequest.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PagedRequest.java @@ -37,7 +37,7 @@ public class PagedRequest { private int page; private int size = DEFAULT_PAGE_SIZE; - private List sorts = new ArrayList<>(); + private final List sorts = new ArrayList<>(); public Optional getRequest() { return Optional.ofNullable(request); diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java index 2787aada393..51b394b1506 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java @@ -45,7 +45,7 @@ public Page fetchPage(final JdbcTemplate jt, final String sqlFetchRows, f // determine how many rows are available final String sqlCountRows = sqlGenerator.countLastExecutedQueryResult(sqlFetchRows); - final int totalFilteredRecords; + final Integer totalFilteredRecords; if (databaseTypeResolver.isMySQL()) { totalFilteredRecords = jt.queryForObject(sqlCountRows, Integer.class); // NOSONAR } else { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JavaType.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JavaType.java index 56b31d23665..3d1536c7bee 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JavaType.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JavaType.java @@ -91,9 +91,7 @@ public enum JavaType { OBJECT(Object.class), // ; - public static final JavaType[] VALUES = values(); - - private static final Map, JavaType> BY_TYPE = Arrays.stream(VALUES).filter(e -> e.type != null) + private static final Map, JavaType> BY_TYPE = Arrays.stream(values()).filter(e -> e.type != null) .collect(Collectors.toMap(JavaType::getTypeClass, v -> v)); private final Class type; @@ -244,9 +242,9 @@ public static JavaType forType(Class type) { // have to do this first to catch custom collection and map types; // on resolve we figure out if these custom types are // persistence-capable - for (Class aType : BY_TYPE.keySet()) { - if (aType.isAssignableFrom(type)) { - return BY_TYPE.get(aType); + for (Map.Entry, JavaType> entry : BY_TYPE.entrySet()) { + if (entry.getKey().isAssignableFrom(type)) { + return entry.getValue(); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java index aea1339f6c4..2eb35d22ebe 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/JdbcJavaType.java @@ -136,11 +136,19 @@ public static JdbcJavaType getByTypeName(@NotNull DatabaseType dialect, String n for (JdbcJavaType type : values()) { DialectType dialectType = type.getDialectType(dialect); if (dialectType.getNameResolved().equals(name)) { + // NOTE: make MySQL systems happy aka TINYINT vs BOOLEAN issue! + if (type.canBooleanType(dialect)) { + return BOOLEAN; + } return type; } if (dialectType.alterNames != null) { for (String alterName : dialectType.alterNames) { if (alterName.equals(name)) { + // NOTE: make MySQL systems happy aka TINYINT vs BOOLEAN issue! + if (type.canBooleanType(dialect)) { + return BOOLEAN; + } return type; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationWritePlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationWritePlatformServiceImpl.java index 77e2793034c..ddaec0514a2 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationWritePlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationWritePlatformServiceImpl.java @@ -48,14 +48,15 @@ public CommandProcessingResult updateConfigurations(final JsonCommand command) { final Map changes = new HashMap<>(); final Map changedConfigurations = new HashMap<>(); final List modifiedConfigurations = new ArrayList<>(); - for (final String eventType : commandConfigurations.keySet()) { + + for (Map.Entry entry : commandConfigurations.entrySet()) { final ExternalEventConfiguration configuration = repository - .findExternalEventConfigurationByTypeWithNotFoundDetection(eventType); - final boolean canEnable = commandConfigurations.get(eventType).booleanValue(); - configuration.setEnabled(canEnable); - changedConfigurations.put(eventType, canEnable); + .findExternalEventConfigurationByTypeWithNotFoundDetection(entry.getKey()); + configuration.setEnabled(entry.getValue()); + changedConfigurations.put(entry.getKey(), entry.getValue()); modifiedConfigurations.add(configuration); } + if (!modifiedConfigurations.isEmpty()) { this.repository.saveAll(modifiedConfigurations); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java index f3c63de8bba..cf5c2ee86a0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.infrastructure.security.utils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -28,6 +29,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -48,17 +50,18 @@ public ColumnValidator(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO: fix this!") private void validateColumn(Map> tableColumnMap) { Connection connection = null; + try { - connection = this.jdbcTemplate.getDataSource().getConnection(); + connection = Objects.requireNonNull(this.jdbcTemplate.getDataSource()).getConnection(); DatabaseMetaData dbMetaData = connection.getMetaData(); - ResultSet resultSet = null; for (Map.Entry> entry : tableColumnMap.entrySet()) { Set columns = entry.getValue(); - resultSet = dbMetaData.getColumns(null, null, entry.getKey(), null); + ResultSet resultSet = dbMetaData.getColumns(null, null, entry.getKey(), null); Set tableColumns = getTableColumns(resultSet); - if (columns.size() > 0 && tableColumns.size() == 0) { + if (!columns.isEmpty() && tableColumns.isEmpty()) { throw new SQLInjectionException(); } for (String requestedColumn : columns) { @@ -111,17 +114,19 @@ public void validateSqlInjection(String schema, String... conditions) { private static Map> getTableColumnMap(String schema, Map> tableColumnAliasMap) { Map> tableColumnMap = new HashMap<>(); schema = schema.substring(schema.indexOf("from")); - for (String alias : tableColumnAliasMap.keySet()) { - int index = schema.indexOf(" " + alias + " "); + + for (Map.Entry> entry : tableColumnAliasMap.entrySet()) { + int index = schema.indexOf(" " + entry.getKey() + " "); if (index > -1) { - int startPos = 0; + int startPos; startPos = schema.substring(0, index - 1).lastIndexOf(' ', index); - Set columns = tableColumnAliasMap.get(alias); + Set columns = entry.getValue(); tableColumnMap.put(schema.substring(startPos, index).trim(), columns); } else { throw new SQLInjectionException(); } } + return tableColumnMap; } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java index 75ebcb027ea..74c540b38e6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java @@ -38,10 +38,16 @@ private SQLInjectionValidator() { private static final String SQL_PATTERN = "[a-zA-Z_=,\\-:'!><.?\"`% ()0-9*\n\r]*"; + // TODO: see here https://rails-sqli.org for and + // https://larrysteinle.com/2011/02/20/use-regular-expressions-to-detect-sql-code-injection more examples + private static final List INJECTION_PATTERNS = List.of("(?i).*[or|and]\s*[\"']?-1[\"']?\\s*(-*).*", + "(?i).*\\s+[\"']?(\\d+)[\"']?\\s*=\\s*[\"']?(\\1)[\"']?\\s*(-*).*"); + public static void validateSQLInput(final String sqlSearch) { if (StringUtils.isBlank(sqlSearch)) { return; } + String lowerCaseSQL = sqlSearch.toLowerCase(); List commandsList = List.of(DDL_COMMANDS, DML_COMMANDS, COMMENTS); validateSQLCommands(lowerCaseSQL, commandsList, String::contains); @@ -53,6 +59,7 @@ public static void validateAdhocQuery(final String sqlSearch) { if (StringUtils.isBlank(sqlSearch)) { return; } + String lowerCaseSQL = sqlSearch.toLowerCase().trim(); validateSQLCommand(lowerCaseSQL, DDL_COMMANDS, String::startsWith); validateSQLCommand(lowerCaseSQL, COMMENTS, String::contains); @@ -80,16 +87,8 @@ private static void patternMatchSqlInjection(String sqlSearch, String lowerCaseS // Removing the space before and after '=' operator // String s = " \" OR 1 = 1"; For the cases like this boolean injectionFound = false; - String inputSqlString = lowerCaseSQL; - while (inputSqlString.indexOf(" =") > 0) { // Don't remove space before - // = operator - inputSqlString = inputSqlString.replaceAll(" =", "="); - } - while (inputSqlString.indexOf("= ") > 0) { // Don't remove space after = - // operator - inputSqlString = inputSqlString.replaceAll("= ", "="); - } + String inputSqlString = lowerCaseSQL.replaceAll("\\s*=\\s*", "="); StringTokenizer tokenizer = new StringTokenizer(inputSqlString, " "); while (tokenizer.hasMoreTokens()) { @@ -131,10 +130,19 @@ private static void patternMatchSqlInjection(String sqlSearch, String lowerCaseS } } } + if (injectionFound) { throw new SQLInjectionException(); } + for (String injectionPattern : INJECTION_PATTERNS) { + Pattern pattern = Pattern.compile(injectionPattern); + Matcher matcher = pattern.matcher(sqlSearch); + if (matcher.matches()) { + throw new SQLInjectionException(); + } + } + Pattern pattern = Pattern.compile(SQL_PATTERN); Matcher matcher = pattern.matcher(sqlSearch); if (!matcher.matches()) { diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java b/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java index 85abe07a6ef..b07d29932e6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/HolidayStatusType.java @@ -31,20 +31,21 @@ public enum HolidayStatusType { private final Integer value; private final String code; - public static HolidayStatusType fromInt(final Integer type) { - HolidayStatusType enumeration = HolidayStatusType.INVALID; - switch (type) { + public static HolidayStatusType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { case 100: - enumeration = HolidayStatusType.PENDING_FOR_ACTIVATION; - break; + return PENDING_FOR_ACTIVATION; case 300: - enumeration = HolidayStatusType.ACTIVE; - break; + return ACTIVE; case 600: - enumeration = HolidayStatusType.DELETED; - break; + return DELETED; + default: + return INVALID; } - return enumeration; } HolidayStatusType(final Integer value, final String code) { @@ -52,10 +53,6 @@ public static HolidayStatusType fromInt(final Integer type) { this.code = code; } - public boolean hasStateOf(final HolidayStatusType state) { - return this.value.equals(state.getValue()); - } - public Integer getValue() { return this.value; } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java b/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java index 6a9e8e3cba0..ea165f194f5 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java @@ -31,17 +31,19 @@ public enum RescheduleType { this.code = code; } - public static RescheduleType fromInt(int rescheduleTypeValue) { - RescheduleType enumerration = RescheduleType.INVALID; - switch (rescheduleTypeValue) { + public static RescheduleType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { case 1: - enumerration = RescheduleType.RESCHEDULETONEXTREPAYMENTDATE; - break; + return RESCHEDULETONEXTREPAYMENTDATE; case 2: - enumerration = RescheduleType.RESCHEDULETOSPECIFICDATE; - break; + return RESCHEDULETOSPECIFICDATE; + default: + return INVALID; } - return enumerration; } public boolean isRescheduleToSpecificDate() { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java index 6c2108d0910..e70dfab5498 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.portfolio.account; -import java.util.ArrayList; -import java.util.List; - public enum PortfolioAccountType { INVALID(0, "accountType.invalid"), // @@ -43,20 +40,9 @@ public String getCode() { return this.code; } - public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final PortfolioAccountType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); - } - public static PortfolioAccountType fromInt(final Integer type) { - PortfolioAccountType enumType = PortfolioAccountType.INVALID; + PortfolioAccountType enumType = INVALID; if (type != null) { switch (type) { case 1: @@ -65,16 +51,20 @@ public static PortfolioAccountType fromInt(final Integer type) { case 2: enumType = SAVINGS; break; + default: + enumType = INVALID; } } return enumType; } + // TODO: bad practice and unnecessary code! why not just use the enum values themselves!?! public boolean isSavingsAccount() { - return this.value.equals(2); + return this.equals(SAVINGS); } + // TODO: bad practice and unnecessary code! why not just use the enum values themselves!?! public boolean isLoanAccount() { - return this.value.equals(1); + return this.equals(LOAN); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java index aa9fed213af..1b4f568b534 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java @@ -272,12 +272,14 @@ public static String getRRuleReadable(final LocalDate startDate, final String re humanReadable += " on "; final WeekDayList weekDayList = recur.getDayList(); + StringBuilder sb = new StringBuilder(); for (@SuppressWarnings("rawtypes") final Iterator iterator = weekDayList.iterator(); iterator.hasNext();) { final WeekDay weekDay = (WeekDay) iterator.next(); - humanReadable += DayNameEnum.from(weekDay.getDay().name()).getCode(); + sb.append(DayNameEnum.from(weekDay.getDay().name()).getCode()); } + humanReadable += sb.toString(); } else if (recur.getFrequency().equals(Recur.Frequency.MONTHLY)) { NumberList nthDays = recur.getSetPosList(); @@ -684,13 +686,15 @@ public static List createIntegerListFromQueryParameter(final String cal * @return */ public static String getSqlCalendarTypeOptionsInString(final List calendarTypeOptions) { - String sqlCalendarTypeOptions = ""; - final int size = calendarTypeOptions.size(); - for (int i = 0; i < size - 1; i++) { - sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ","; + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < calendarTypeOptions.size() - 1; i++) { + sb.append(calendarTypeOptions.get(i).toString() + ","); } - sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString(); - return sqlCalendarTypeOptions; + + sb.append(calendarTypeOptions.get(calendarTypeOptions.size() - 1).toString()); + + return sb.toString(); } public static LocalDate getRecentEligibleMeetingDate(final String recurringRule, final LocalDate seedDate, diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java index 32d58ccbc36..20eca6bfe2e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInMonthType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.common.domain; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * @@ -51,15 +50,9 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final DaysInMonthType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } public static DaysInMonthType fromInt(final Integer type) { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java index d08fff7a9c4..80a42d48417 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/DaysInYearType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.common.domain; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * @@ -55,15 +54,9 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final DaysInYearType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } public static DaysInYearType fromInt(final Integer type) { diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java index 4a447863740..f2e8561aae9 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/domain/PeriodFrequencyType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.common.domain; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; public enum PeriodFrequencyType { @@ -46,62 +45,59 @@ public String getCode() { return this.code; } - public static PeriodFrequencyType fromInt(final Integer frequency) { - PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.INVALID; - if (frequency != null) { - switch (frequency) { - case 0: - repaymentFrequencyType = PeriodFrequencyType.DAYS; - break; - case 1: - repaymentFrequencyType = PeriodFrequencyType.WEEKS; - break; - case 2: - repaymentFrequencyType = PeriodFrequencyType.MONTHS; - break; - case 3: - repaymentFrequencyType = PeriodFrequencyType.YEARS; - break; - case 4: - repaymentFrequencyType = PeriodFrequencyType.WHOLE_TERM; - break; - } + public static PeriodFrequencyType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 0: + return DAYS; + case 1: + return WEEKS; + case 2: + return MONTHS; + case 3: + return YEARS; + case 4: + return WHOLE_TERM; + default: + return INVALID; } - return repaymentFrequencyType; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isMonthly() { - return this.value.equals(PeriodFrequencyType.MONTHS.getValue()); + return this.equals(MONTHS); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isYearly() { - return this.value.equals(PeriodFrequencyType.YEARS.getValue()); + return this.equals(YEARS); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isWeekly() { - return this.value.equals(PeriodFrequencyType.WEEKS.getValue()); + return this.equals(WEEKS); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isDaily() { - return this.value.equals(PeriodFrequencyType.DAYS.getValue()); + return this.equals(DAYS); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isWholeTerm() { - return this.value.equals(PeriodFrequencyType.WHOLE_TERM.getValue()); + return this.equals(WHOLE_TERM); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return this.value.equals(PeriodFrequencyType.INVALID.getValue()); + return this.equals(INVALID); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final PeriodFrequencyType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java index aded4a0286a..95138389cd3 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * An enumeration of different options available on account closure {@link FixedDepositAccount} & @@ -49,63 +48,57 @@ public String getCode() { return this.code; } - public static DepositAccountOnClosureType fromInt(final Integer closureTypeValue) { - - if (closureTypeValue == null) { - return DepositAccountOnClosureType.INVALID; + public static DepositAccountOnClosureType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.INVALID; - switch (closureTypeValue) { + switch (v) { case 100: - accountOnClosureType = DepositAccountOnClosureType.WITHDRAW_DEPOSIT; - break; + return WITHDRAW_DEPOSIT; case 200: - accountOnClosureType = DepositAccountOnClosureType.TRANSFER_TO_SAVINGS; - break; + return TRANSFER_TO_SAVINGS; case 300: - accountOnClosureType = DepositAccountOnClosureType.REINVEST_PRINCIPAL_AND_INTEREST; - break; + return REINVEST_PRINCIPAL_AND_INTEREST; case 400: - accountOnClosureType = DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY; - break; + return REINVEST_PRINCIPAL_ONLY; + default: + return INVALID; } - return accountOnClosureType; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isWithdarwDeposit() { - return this.value.equals(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue()); + return this.equals(WITHDRAW_DEPOSIT); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isTransferToSavings() { - return this.value.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue()); + return this.equals(TRANSFER_TO_SAVINGS); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isReinvest() { - return this.value.equals(DepositAccountOnClosureType.REINVEST_PRINCIPAL_AND_INTEREST.getValue()) - || this.value.equals(DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY.getValue()); + return this.equals(REINVEST_PRINCIPAL_AND_INTEREST) || this.equals(REINVEST_PRINCIPAL_ONLY); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isReinvestPrincipal() { - return this.value.equals(DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY.getValue()); + return this.equals(REINVEST_PRINCIPAL_ONLY); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isReinvestPrincipalAndInterest() { - return this.value.equals(DepositAccountOnClosureType.REINVEST_PRINCIPAL_AND_INTEREST.getValue()); + return this.equals(REINVEST_PRINCIPAL_AND_INTEREST); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return this.value.equals(DepositAccountOnClosureType.INVALID.getValue()); + return this.equals(INVALID); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final DepositAccountOnClosureType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java index 816d22d4cd9..ee293757d43 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnHoldTransactionType.java @@ -43,31 +43,28 @@ public String getCode() { return this.code; } - public static DepositAccountOnHoldTransactionType fromInt(final Integer transactionType) { - - if (transactionType == null) { - return DepositAccountOnHoldTransactionType.INVALID; + public static DepositAccountOnHoldTransactionType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - DepositAccountOnHoldTransactionType savingsAccountTransactionType = DepositAccountOnHoldTransactionType.INVALID; - switch (transactionType) { + switch (v) { case 1: - savingsAccountTransactionType = DepositAccountOnHoldTransactionType.HOLD; - break; + return HOLD; case 2: - savingsAccountTransactionType = DepositAccountOnHoldTransactionType.RELEASE; - break; - + return RELEASE; + default: + return INVALID; } - return savingsAccountTransactionType; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isHold() { - return this.value.equals(DepositAccountOnHoldTransactionType.HOLD.getValue()); + return this.equals(HOLD); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isRelease() { - return this.value.equals(DepositAccountOnHoldTransactionType.RELEASE.getValue()); + return this.equals(RELEASE); } - } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java index f9b3178b61a..ca60bb51b53 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountType.java @@ -48,44 +48,48 @@ public String getCode() { return this.code; } - public static DepositAccountType fromInt(final Integer transactionType) { - - if (transactionType == null) { - return DepositAccountType.INVALID; + public static DepositAccountType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - DepositAccountType depositAccountType = DepositAccountType.INVALID; - switch (transactionType) { + switch (v) { case 100: - depositAccountType = DepositAccountType.SAVINGS_DEPOSIT; - break; + return SAVINGS_DEPOSIT; case 200: - depositAccountType = DepositAccountType.FIXED_DEPOSIT; - break; + return FIXED_DEPOSIT; case 300: - depositAccountType = DepositAccountType.RECURRING_DEPOSIT; - break; + return RECURRING_DEPOSIT; case 400: - depositAccountType = DepositAccountType.CURRENT_DEPOSIT; - break; + return CURRENT_DEPOSIT; + default: + return INVALID; } - return depositAccountType; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isSavingsDeposit() { - return this.value.equals(DepositAccountType.SAVINGS_DEPOSIT.getValue()); + return this.equals(SAVINGS_DEPOSIT); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isFixedDeposit() { - return this.value.equals(DepositAccountType.FIXED_DEPOSIT.getValue()); + return this.equals(FIXED_DEPOSIT); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isRecurringDeposit() { - return this.value.equals(DepositAccountType.RECURRING_DEPOSIT.getValue()); + return this.equals(RECURRING_DEPOSIT); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isCurrentDeposit() { - return this.value.equals(DepositAccountType.CURRENT_DEPOSIT.getValue()); + return this.equals(CURRENT_DEPOSIT); + } + + // TODO: why not just use the enum values... just more boilerplate code here!! + public boolean isInvalid() { + return this.equals(INVALID); } @Override @@ -94,28 +98,15 @@ public String toString() { } public String resourceName() { - - String resourceName; - switch (this) { case FIXED_DEPOSIT: - resourceName = DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME; - break; + return DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME; case RECURRING_DEPOSIT: - resourceName = DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME; - break; + return DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME; case SAVINGS_DEPOSIT: - resourceName = DepositsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; - break; + return DepositsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; default: - resourceName = "INVALID"; - break; + return "INVALID"; } - - return resourceName; - } - - public boolean isInvalid() { - return this.value.equals(DepositAccountType.INVALID.value); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/PreClosurePenalInterestOnType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/PreClosurePenalInterestOnType.java index 8bd52c8535f..f66656e3875 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/PreClosurePenalInterestOnType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/PreClosurePenalInterestOnType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * An enumeration of supported calendar periods used in savings. @@ -45,41 +44,38 @@ public String getCode() { return this.code; } - public static PreClosurePenalInterestOnType fromInt(final Integer type) { - PreClosurePenalInterestOnType penalInterestType = PreClosurePenalInterestOnType.INVALID; - if (type != null) { - switch (type) { - case 1: - penalInterestType = PreClosurePenalInterestOnType.WHOLE_TERM; - break; - case 2: - penalInterestType = PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL; - break; - } + public static PreClosurePenalInterestOnType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - return penalInterestType; - } - public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final PreClosurePenalInterestOnType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } + switch (v) { + case 1: + return WHOLE_TERM; + case 2: + return TILL_PREMATURE_WITHDRAWAL; + default: + return INVALID; } + } - return values.toArray(); + // TODO: do we really need this?!? + public static Object[] integerValues() { + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return this.value.equals(PreClosurePenalInterestOnType.INVALID.value); + return this.equals(INVALID); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isWholeTerm() { - return this.value.equals(PreClosurePenalInterestOnType.WHOLE_TERM.getValue()); + return this.equals(WHOLE_TERM); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isTillPrematureWithdrawal() { - return this.value.equals(PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL.getValue()); + return this.equals(TILL_PREMATURE_WITHDRAWAL); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.java index 1462121f02d..2994b0711ba 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/RecurringDepositType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * An enumeration of supported calendar periods used in savings. @@ -45,32 +44,27 @@ public String getCode() { return this.code; } - public static RecurringDepositType fromInt(final Integer type) { - RecurringDepositType rdType = RecurringDepositType.INVALID; - if (type != null) { - switch (type) { - case 1: - rdType = RecurringDepositType.VOLUNTARY; - break; - case 2: - rdType = RecurringDepositType.MANDATORY; - break; - } + public static RecurringDepositType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - return rdType; - } - public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final RecurringDepositType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } + switch (v) { + case 1: + return VOLUNTARY; + case 2: + return MANDATORY; + default: + return INVALID; } + } - return values.toArray(); + // TODO: do we really need this?!? + public static Object[] integerValues() { + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { return this.value.equals(RecurringDepositType.INVALID.value); } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java index e7e111d185f..5c24514d1f4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java @@ -52,9 +52,7 @@ public enum SavingsAccountTransactionType { AMOUNT_HOLD(20, "savingsAccountTransactionType.onHold", TransactionEntryType.DEBIT), // AMOUNT_RELEASE(21, "savingsAccountTransactionType.release", TransactionEntryType.CREDIT); // - public static final SavingsAccountTransactionType[] VALUES = values(); - - private static final Map BY_ID = Arrays.stream(VALUES) + private static final Map BY_ID = Arrays.stream(values()) .collect(Collectors.toMap(SavingsAccountTransactionType::getValue, v -> v)); private final int value; @@ -224,6 +222,6 @@ public boolean isDebit() { @NotNull public static List getFiltered(Predicate filter) { - return Arrays.stream(VALUES).filter(filter).toList(); + return Arrays.stream(values()).filter(filter).toList(); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java index c6607c9d136..860af9ab639 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java @@ -35,6 +35,7 @@ public class SavingsApiConstants { public static final String withdrawnByApplicantAction = ".withdrawnByApplicant"; public static final String activateAction = ".activate"; public static final String modifyApplicationAction = ".modify"; + public static final String undoActivateAction = ".undoactivate"; public static final String deleteApplicationAction = ".delete"; public static final String undoTransactionAction = ".undotransaction"; public static final String applyAnnualFeeTransactionAction = ".applyannualfee"; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java index ba4b61296a8..3a326cd723a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsCompoundingInterestPeriodType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** *

@@ -28,18 +27,15 @@ */ public enum SavingsCompoundingInterestPeriodType { - INVALID(0, "savingsCompoundingInterestPeriodType.invalid"), // - DAILY(1, "savingsCompoundingInterestPeriodType.daily"), // - // WEEKLY(2, "savingsCompoundingInterestPeriodType.weekly"), // - // BIWEEKLY(3, "savingsCompoundingInterestPeriodType.biweekly"), // + INVALID(0, "savingsCompoundingInterestPeriodType.invalid"), DAILY(1, "savingsCompoundingInterestPeriodType.daily"), + // WEEKLY(2, "savingsCompoundingInterestPeriodType.weekly"), + // BIWEEKLY(3, "savingsCompoundingInterestPeriodType.biweekly"), MONTHLY(4, "savingsCompoundingInterestPeriodType.monthly"), - QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), // - BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), // - ANNUAL(7, "savingsCompoundingInterestPeriodType.annual"); // + QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), ANNUAL(7, + "savingsCompoundingInterestPeriodType.annual"); - // NO_COMPOUNDING_SIMPLE_INTEREST(8, - // "savingsCompoundingInterestPeriodType.nocompounding"); + // NO_COMPOUNDING_SIMPLE_INTEREST(8, "savingsCompoundingInterestPeriodType.nocompounding"); private final Integer value; private final String code; @@ -57,50 +53,35 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SavingsCompoundingInterestPeriodType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - public static SavingsCompoundingInterestPeriodType fromInt(final Integer type) { - SavingsCompoundingInterestPeriodType repaymentFrequencyType = SavingsCompoundingInterestPeriodType.INVALID; - if (type != null) { - switch (type) { - case 1: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.DAILY; - break; - case 2: - // repaymentFrequencyType = - // SavingsCompoundingInterestPeriodType.WEEKLY; - break; - case 3: - // repaymentFrequencyType = - // SavingsCompoundingInterestPeriodType.BIWEEKLY; - break; - case 4: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.MONTHLY; - break; - case 5: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.QUATERLY; - break; - case 6: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.BI_ANNUAL; - break; - case 7: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.ANNUAL; - break; - case 8: - // repaymentFrequencyType = - // SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST; - break; - } + public static SavingsCompoundingInterestPeriodType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 1: + return DAILY; + // case 2: + // return WEEKLY; + // case 3: + // return BIWEEKLY; + case 4: + return MONTHLY; + case 5: + return QUATERLY; + case 6: + return BI_ANNUAL; + case 7: + return ANNUAL; + // case 8: + // return NO_COMPOUNDING_SIMPLE_INTEREST; + default: + return INVALID; } - return repaymentFrequencyType; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java index 616f017ab2d..e495b7fbd70 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationDaysInYearType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * @@ -51,29 +50,23 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SavingsInterestCalculationDaysInYearType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - public static SavingsInterestCalculationDaysInYearType fromInt(final Integer type) { - SavingsInterestCalculationDaysInYearType repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.INVALID; - if (type != null) { - switch (type) { - case 360: - repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.DAYS_360; - break; - case 365: - repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.DAYS_365; - break; - } + public static SavingsInterestCalculationDaysInYearType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 360: + return DAYS_360; + case 365: + return DAYS_365; + default: + return INVALID; } - return repaymentFrequencyType; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java index 72701c72ff1..c3f63ddfb44 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsInterestCalculationType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * @@ -64,29 +63,23 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SavingsInterestCalculationType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - public static SavingsInterestCalculationType fromInt(final Integer type) { - SavingsInterestCalculationType repaymentFrequencyType = SavingsInterestCalculationType.INVALID; - if (type != null) { - switch (type) { - case 1: - repaymentFrequencyType = SavingsInterestCalculationType.DAILY_BALANCE; - break; - case 2: - repaymentFrequencyType = SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE; - break; - } + public static SavingsInterestCalculationType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 1: + return DAILY_BALANCE; + case 2: + return AVERAGE_DAILY_BALANCE; + default: + return INVALID; } - return repaymentFrequencyType; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java index 0fb4ccbc974..cfebadce205 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPeriodFrequencyType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * An enumeration of supported calendar periods used in savings. @@ -48,39 +47,32 @@ public String getCode() { return this.code; } - public static SavingsPeriodFrequencyType fromInt(final Integer type) { - SavingsPeriodFrequencyType repaymentFrequencyType = SavingsPeriodFrequencyType.INVALID; - if (type != null) { - switch (type) { - case 0: - repaymentFrequencyType = SavingsPeriodFrequencyType.DAYS; - break; - case 1: - repaymentFrequencyType = SavingsPeriodFrequencyType.WEEKS; - break; - case 2: - repaymentFrequencyType = SavingsPeriodFrequencyType.MONTHS; - break; - case 3: - repaymentFrequencyType = SavingsPeriodFrequencyType.YEARS; - break; - } + public static SavingsPeriodFrequencyType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 0: + return DAYS; + case 1: + return WEEKS; + case 2: + return MONTHS; + case 3: + return YEARS; + default: + return INVALID; } - return repaymentFrequencyType; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return this.value.equals(SavingsPeriodFrequencyType.INVALID.value); + return this.equals(INVALID); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SavingsPeriodFrequencyType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java index aefdb5f080b..4da0987a676 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * The interest posting period is the span of time at the end of which savings earned but not yet credited/posted in a @@ -49,38 +48,29 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SavingsPostingInterestPeriodType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - public static SavingsPostingInterestPeriodType fromInt(final Integer type) { - SavingsPostingInterestPeriodType repaymentFrequencyType = SavingsPostingInterestPeriodType.INVALID; - if (type != null) { - switch (type) { - case 1: - repaymentFrequencyType = SavingsPostingInterestPeriodType.DAILY; - break; - case 4: - repaymentFrequencyType = SavingsPostingInterestPeriodType.MONTHLY; - break; - case 5: - repaymentFrequencyType = SavingsPostingInterestPeriodType.QUATERLY; - break; - case 6: - repaymentFrequencyType = SavingsPostingInterestPeriodType.BIANNUAL; - break; - case 7: - repaymentFrequencyType = SavingsPostingInterestPeriodType.ANNUAL; - break; - } + public static SavingsPostingInterestPeriodType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 1: + return DAILY; + case 4: + return MONTHLY; + case 5: + return QUATERLY; + case 6: + return BIANNUAL; + case 7: + return ANNUAL; + default: + return INVALID; } - return repaymentFrequencyType; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.java index e8b1f801894..7aaaed0d854 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsWithdrawalFeesType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.savings; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; public enum SavingsWithdrawalFeesType { @@ -43,28 +42,23 @@ public String getCode() { return this.code; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SavingsWithdrawalFeesType enumType : values()) { - if (enumType.getValue() > 0) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - public static SavingsWithdrawalFeesType fromInt(final Integer type) { + public static SavingsWithdrawalFeesType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } - SavingsWithdrawalFeesType withdrawalFeeType = SavingsWithdrawalFeesType.INVALID; - switch (type) { + switch (v) { case 1: - withdrawalFeeType = FLAT; - break; + return FLAT; case 2: - withdrawalFeeType = PERCENT_OF_AMOUNT; - break; + return PERCENT_OF_AMOUNT; + default: + return INVALID; } - return withdrawalFeeType; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java index db2972cfbb6..cce8954dead 100644 --- a/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java +++ b/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java @@ -657,6 +657,10 @@ public void validateHasCheckerPermissionTo(final String function) { } } + public boolean isCheckerSuperUser() { + return hasPermissionTo("CHECKER_SUPER_USER"); + } + public void validateHasDatatableReadPermission(final String datatable) { if (hasNotPermissionForDatatable(datatable, "READ")) { throw new NoAuthorizationException("Not authorised to read datatable: " + datatable); diff --git a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java index 91e4095a16c..04ba9aa541f 100644 --- a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java +++ b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java @@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandExceptionMapper; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,6 +49,11 @@ public void setUp() { ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void testInconsistentStatus() { IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper(); diff --git a/fineract-doc/.asciidoctorconfig b/fineract-doc/.asciidoctorconfig index b8b62167628..c6fcc35fe2a 100644 --- a/fineract-doc/.asciidoctorconfig +++ b/fineract-doc/.asciidoctorconfig @@ -1,5 +1,5 @@ -:years: 2015-2022 -:revnumber: 1.8.0 +:years: 2015-2024 +:revnumber: 1.9.0-xxxx :draft: true :rootdir: {asciidoctorconfigdir}/.. :generated: {asciidoctorconfigdir}/build/generated/asciidoc diff --git a/fineract-doc/build.gradle b/fineract-doc/build.gradle index d9464d20bfa..7f1c6bb679a 100644 --- a/fineract-doc/build.gradle +++ b/fineract-doc/build.gradle @@ -30,7 +30,7 @@ asciidoctorj { generated: "${buildDir}/generated/asciidoc", imagesdir: "${projectDir}/src/docs/en/images", diagramsdir: "${projectDir}/src/docs/en/diagrams", - years: '2015-2022', + years: '2015-2024', revnumber: "${project.version}".toString(), rootdir: "${rootDir}".toString(), baseurl: 'fineract.apache.org', diff --git a/fineract-doc/src/docs/en/config.adoc b/fineract-doc/src/docs/en/config.adoc index dd47f4ec746..fe60469584d 100644 --- a/fineract-doc/src/docs/en/config.adoc +++ b/fineract-doc/src/docs/en/config.adoc @@ -14,7 +14,7 @@ :source-highlighter: coderay :experimental: :source-language: java -:years: 2015-2022 +:years: 2015-2024 :lang: en :encoding: utf-8 :linkattrs: diff --git a/fineract-investor/dependencies.gradle b/fineract-investor/dependencies.gradle index 44a84d1d9be..943673f452e 100644 --- a/fineract-investor/dependencies.gradle +++ b/fineract-investor/dependencies.gradle @@ -52,7 +52,7 @@ dependencies { 'org.springdoc:springdoc-openapi-starter-webmvc-ui', 'org.mapstruct:mapstruct', - 'io.github.resilience4j:resilience4j-spring-boot2', + 'io.github.resilience4j:resilience4j-spring-boot3', ) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerJournalEntryServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerJournalEntryServiceImpl.java index dcd41fcb2b4..186bb64c70c 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerJournalEntryServiceImpl.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerJournalEntryServiceImpl.java @@ -22,7 +22,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.accounting.journalentry.domain.JournalEntry; -import org.apache.fineract.infrastructure.event.business.BusinessEventListener; import org.apache.fineract.infrastructure.event.business.domain.journalentry.LoanJournalEntryCreatedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition; @@ -46,26 +45,17 @@ public class ExternalAssetOwnerJournalEntryServiceImpl implements ExternalAssetO @PostConstruct public void addListeners() { - businessEventNotifierService.addPostBusinessEventListener(LoanJournalEntryCreatedBusinessEvent.class, - new HandleLoanJournalEntryCreatedBusinessEvent()); - } - - private final class HandleLoanJournalEntryCreatedBusinessEvent implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanJournalEntryCreatedBusinessEvent event) { + businessEventNotifierService.addPostBusinessEventListener(LoanJournalEntryCreatedBusinessEvent.class, event -> { JournalEntry journalEntry = event.get(); - createMapping(journalEntry); - } - private void createMapping(JournalEntry journalEntry) { Long loanId = loanTransactionRepository.findLoanIdById(journalEntry.getLoanTransactionId()).orElseThrow(); + externalAssetOwnerTransferLoanMappingRepository.findByLoanId(loanId).ifPresent(transferLoanMapping -> { ExternalAssetOwnerJournalEntryMapping mapping = new ExternalAssetOwnerJournalEntryMapping(); mapping.setJournalEntry(journalEntry); mapping.setOwner(transferLoanMapping.getOwnerTransfer().getOwner()); externalAssetOwnerJournalEntryMappingRepository.saveAndFlush(mapping); }); - } + }); } } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanStatusChangePlatformServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanStatusChangePlatformServiceImpl.java index e1855f0b178..efe3d72140e 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanStatusChangePlatformServiceImpl.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanStatusChangePlatformServiceImpl.java @@ -21,7 +21,6 @@ import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService; -import org.apache.fineract.infrastructure.event.business.BusinessEventListener; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -39,19 +38,12 @@ public class ExternalAssetOwnerLoanStatusChangePlatformServiceImpl implements Ex @PostConstruct public void addListeners() { - businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, - new ExternalAssetOwnerLoanStatusChangedListener()); - } - - private final class ExternalAssetOwnerLoanStatusChangedListener implements BusinessEventListener { - - @Override - public void onBusinessEvent(LoanStatusChangedBusinessEvent event) { + businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, event -> { final Loan loan = event.get(); if (configurationReadPlatformService.retrieveGlobalConfiguration(ASSET_EXTERNALIZATION_OF_NON_ACTIVE_LOANS).isEnabled() && (loan.isClosed() || loan.getStatus().isOverpaid())) { loanAccountOwnerTransferService.handleLoanClosedOrOverpaid(loan); } - } + }); } } diff --git a/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java b/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java index bd1eab0fc16..8286eed6f22 100644 --- a/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java +++ b/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java @@ -54,6 +54,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -90,6 +91,11 @@ public void setUp() { externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenLoanNoTransfer() { // given diff --git a/fineract-loan/dependencies.gradle b/fineract-loan/dependencies.gradle index c6f4d7c1a65..bd2a15867e5 100644 --- a/fineract-loan/dependencies.gradle +++ b/fineract-loan/dependencies.gradle @@ -47,7 +47,7 @@ dependencies { 'org.springdoc:springdoc-openapi-starter-webmvc-ui', 'org.mapstruct:mapstruct', - 'io.github.resilience4j:resilience4j-spring-boot2', + 'io.github.resilience4j:resilience4j-spring-boot3', 'org.apache.httpcomponents:httpcore', ) compileOnly 'org.projectlombok:lombok' diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java index 597791b35cc..cc0d8b3ef0e 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/collateral/api/CollateralApiConstants.java @@ -18,8 +18,9 @@ */ package org.apache.fineract.portfolio.collateral.api; -import java.util.HashSet; +import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; public final class CollateralApiConstants { @@ -43,16 +44,8 @@ public enum CollateralJSONinputParams { this.value = value; } - private static final Set values = new HashSet<>(); - - static { - for (final CollateralJSONinputParams type : CollateralJSONinputParams.values()) { - values.add(type.value); - } - } - public static Set getAllValues() { - return values; + return Arrays.stream(values()).map(CollateralJSONinputParams::getValue).collect(Collectors.toSet()); } @Override diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanChargeCommand.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanChargeCommand.java index 4b906bd0c51..a135c5ab5aa 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanChargeCommand.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/command/LoanChargeCommand.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.command; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.math.BigDecimal; import java.time.LocalDate; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; @@ -49,6 +50,7 @@ public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal am } @Override + @SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals") public int compareTo(final LoanChargeCommand o) { int comparison = this.chargeId.compareTo(o.chargeId); if (comparison == 0) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 52ca276fed6..c258ef1eea0 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -688,7 +689,7 @@ protected void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, if (loanTransaction.isChargePayment()) { for (final LoanChargePaidBy chargePaidBy : chargesPaidBies) { LoanCharge loanCharge = chargePaidBy.getLoanCharge(); - if (loanCharge.getId().equals(unpaidCharge.getId())) { + if (loanCharge != null && Objects.equals(loanCharge.getId(), unpaidCharge.getId())) { chargePaidBy.setAmount(amountPaidTowardsCharge.getAmount()); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java index 5c5a4a0437d..4e69c90c3f8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.time.LocalDate; import java.time.OffsetDateTime; import java.util.Optional; @@ -81,6 +82,7 @@ private OffsetDateTime getCreatedDateTime() { } @Override + @SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals") public int compareTo(@NotNull ChargeOrTransaction o) { int datePortion = this.getEffectiveDate().compareTo(o.getEffectiveDate()); if (datePortion == 0) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java index 984609ec6f2..f9d065abe6b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java @@ -221,7 +221,6 @@ protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentS List transactionMappings) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); - final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); Money transactionAmountRemaining = transactionAmountUnprocessed; Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index c767aa1a388..f4fb4fec0ce 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -264,22 +264,6 @@ jib { creationTime = 'USE_CURRENT_TIMESTAMP' mainClass = 'org.apache.fineract.ServerApplication' extraClasspath = ['/app/plugins/*'] - jvmFlags = [ - '-Xms1G', - '-XshowSettings:vm', - '-XX:+UseContainerSupport', - '-XX:+UseStringDeduplication', - '-XX:MinRAMPercentage=25', - '-XX:MaxRAMPercentage=80', - '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED', - '--add-opens=java.base/java.lang=ALL-UNNAMED', - '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED', - '--add-opens=java.base/java.io=ALL-UNNAMED', - '--add-opens=java.base/java.security=ALL-UNNAMED', - '--add-opens=java.base/java.util=ALL-UNNAMED', - '--add-opens=java.management/javax.management=ALL-UNNAMED', - '--add-opens=java.naming/javax.naming=ALL-UNNAMED' - ] args = [ '-Duser.home=/tmp', '-Dfile.encoding=UTF-8', @@ -336,6 +320,12 @@ cucumber { tags = 'not @ignore' main = 'io.cucumber.core.cli.Main' shorten = 'argfile' + plugin = [ + 'pretty', + 'html:build/reports/cucumber/report.html', + 'json:build/reports/cucumber/report.json', + 'junit:build/reports/cucumber/report.xml' + ] } tasks.jibDockerBuild.dependsOn(bootJar) diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 60a5e7c824e..75ba8b9d886 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -18,6 +18,12 @@ */ dependencies { + modules { + module("io.swagger.core.v3:swagger-annotations") { + replacedBy("io.swagger.core.v3:swagger-annotations-jakarta") + } + } + implementation(project(path: ':fineract-core')) implementation(project(path: ':fineract-investor')) implementation(project(path: ':fineract-loan')) @@ -96,14 +102,14 @@ dependencies { 'org.springdoc:springdoc-openapi-starter-webmvc-ui', 'org.mapstruct:mapstruct', - 'io.github.resilience4j:resilience4j-spring-boot2', + 'io.github.resilience4j:resilience4j-spring-boot3', ) - implementation ('software.amazon.msk:aws-msk-iam-auth:2.0.0') { + implementation ('software.amazon.msk:aws-msk-iam-auth:2.0.2') { exclude group: 'commons-logging' } - implementation 'io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.11' + implementation 'io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.19' implementation ('org.apache.commons:commons-email') { exclude group: 'com.sun.mail', module: 'javax.mail' @@ -122,7 +128,7 @@ dependencies { implementation ('jakarta.xml.bind:jakarta.xml.bind-api') { exclude group: 'jakarta.activation' } - implementation ('org.apache.activemq:activemq-client') { + implementation ('org.apache.activemq:activemq-client-jakarta') { exclude group: 'org.apache.geronimo.specs' exclude group: 'javax.annotation', module: 'javax.annotation-api' } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java index c107c8a60b3..48b655a251c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java @@ -83,7 +83,7 @@ public class JournalEntriesApiResource { "entityType", "entityId", "createdByUserId", "createdDate", "submittedOnDate", "createdByUserName", "comments", "reversed", "referenceNumber", "currency", "transactionDetails")); - private final String resourceNameForPermission = "JOURNALENTRY"; + private static final String RESOURCE_NAME_FOR_PERMISSION = "JOURNALENTRY"; private final PlatformSecurityContext context; private final JournalEntryReadPlatformService journalEntryReadPlatformService; @@ -125,7 +125,7 @@ public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("runningBalance") @Parameter(description = "runningBalance") final boolean runningBalance, @QueryParam("transactionDetails") @Parameter(description = "transactionDetails") final boolean transactionDetails) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat); @@ -174,7 +174,7 @@ public String retrieveJournalEntryById( @QueryParam("runningBalance") @Parameter(description = "runningBalance") final boolean runningBalance, @QueryParam("transactionDetails") @Parameter(description = "transactionDetails") final boolean transactionDetails) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(transactionDetails, runningBalance); final JournalEntryData glJournalEntryData = this.journalEntryReadPlatformService.retrieveGLJournalEntryById(journalEntryId, @@ -260,7 +260,7 @@ public String retrieveJournalEntries(@QueryParam("offset") final Integer offset, public String retrieveOpeningBalance(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId, @QueryParam("currencyCode") final String currencyCode) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final OfficeOpeningBalancesData officeOpeningBalancesData = this.journalEntryReadPlatformService .retrieveOfficeOpeningBalances(officeId, currencyCode); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index 1272055ca01..49aaf44a50b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -724,10 +724,6 @@ private void createJournalEntriesForChargeOffLoanRepaymentAndWriteOffs(LoanDTO l populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), accountMapForCredit, accountMapForDebit); - } else if (loanTransactionDTO.getTransactionType().isRepayment()) { - populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), - AccrualAccountsForLoan.FUND_SOURCE.getValue(), accountMapForCredit, accountMapForDebit); - } else { populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(), AccrualAccountsForLoan.FUND_SOURCE.getValue(), accountMapForCredit, accountMapForDebit); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java index 6786c8a7fbe..88f5f39c59b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java @@ -701,10 +701,6 @@ private void createJournalEntriesForChargeOffLoanRepayments(LoanDTO loanDTO, Loa } else if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) { populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(), CashAccountsForLoan.GOODWILL_CREDIT.getValue(), accountMapForCredit, accountMapForDebit); - } else if (loanTransactionDTO.getTransactionType().isRepayment()) { - populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(), - CashAccountsForLoan.FUND_SOURCE.getValue(), accountMapForCredit, accountMapForDebit); - } else { populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(), CashAccountsForLoan.FUND_SOURCE.getValue(), accountMapForCredit, accountMapForDebit); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index bfc192996a6..fa1d7273748 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -401,50 +400,46 @@ public String createProvisioningJournalEntries(ProvisioningEntry provisioningEnt } } - Set officeSet = officeMap.keySet(); Map liabilityMap = new HashMap<>(); Map expenseMap = new HashMap<>(); - for (OfficeCurrencyKey key : officeSet) { + for (Map.Entry> entry : officeMap.entrySet()) { liabilityMap.clear(); expenseMap.clear(); - List entries = officeMap.get(key); - for (LoanProductProvisioningEntry entry : entries) { - if (liabilityMap.containsKey(entry.getLiabilityAccount())) { - BigDecimal amount = liabilityMap.get(entry.getLiabilityAccount()); - amount = amount.add(entry.getReservedAmount()); - liabilityMap.put(entry.getLiabilityAccount(), amount); + for (LoanProductProvisioningEntry lppEntry : entry.getValue()) { + if (liabilityMap.containsKey(lppEntry.getLiabilityAccount())) { + BigDecimal amount = liabilityMap.get(lppEntry.getLiabilityAccount()); + amount = amount.add(lppEntry.getReservedAmount()); + liabilityMap.put(lppEntry.getLiabilityAccount(), amount); } else { - BigDecimal amount = BigDecimal.ZERO.add(entry.getReservedAmount()); - liabilityMap.put(entry.getLiabilityAccount(), amount); + BigDecimal amount = BigDecimal.ZERO.add(lppEntry.getReservedAmount()); + liabilityMap.put(lppEntry.getLiabilityAccount(), amount); } - if (expenseMap.containsKey(entry.getExpenseAccount())) { - BigDecimal amount = expenseMap.get(entry.getExpenseAccount()); - amount = amount.add(entry.getReservedAmount()); - expenseMap.put(entry.getExpenseAccount(), amount); + if (expenseMap.containsKey(lppEntry.getExpenseAccount())) { + BigDecimal amount = expenseMap.get(lppEntry.getExpenseAccount()); + amount = amount.add(lppEntry.getReservedAmount()); + expenseMap.put(lppEntry.getExpenseAccount(), amount); } else { - BigDecimal amount = BigDecimal.ZERO.add(entry.getReservedAmount()); - expenseMap.put(entry.getExpenseAccount(), amount); + BigDecimal amount = BigDecimal.ZERO.add(lppEntry.getReservedAmount()); + expenseMap.put(lppEntry.getExpenseAccount(), amount); } } - createJournalEntry(provisioningEntry.getCreatedDate(), provisioningEntry.getId(), key.office, key.currency, liabilityMap, - expenseMap); + createJournalEntry(provisioningEntry.getCreatedDate(), provisioningEntry.getId(), entry.getKey().office, + entry.getKey().currency, liabilityMap, expenseMap); } return "P" + provisioningEntry.getId(); } private void createJournalEntry(LocalDate transactionDate, Long entryId, Office office, String currencyCode, Map liabilityMap, Map expenseMap) { - Set liabilityAccounts = liabilityMap.keySet(); - for (GLAccount account : liabilityAccounts) { - this.helper.createProvisioningCreditJournalEntry(transactionDate, entryId, office, currencyCode, account, - liabilityMap.get(account)); - } - Set expenseAccounts = expenseMap.keySet(); - for (GLAccount account : expenseAccounts) { - this.helper.createProvisioningDebitJournalEntry(transactionDate, entryId, office, currencyCode, account, - expenseMap.get(account)); + for (Map.Entry entry : liabilityMap.entrySet()) { + this.helper.createProvisioningCreditJournalEntry(transactionDate, entryId, office, currencyCode, entry.getKey(), + entry.getValue()); + } + for (Map.Entry entry : expenseMap.entrySet()) { + this.helper.createProvisioningDebitJournalEntry(transactionDate, entryId, office, currencyCode, entry.getKey(), + entry.getValue()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java index 114db8359b4..40e155e4831 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/rule/api/AccountingRuleApiResource.java @@ -86,7 +86,7 @@ public class AccountingRuleApiResource { "allowedCreditTagOptions", "allowedDebitTagOptions", "debitTags", "creditTags", "creditAccounts", "debitAccounts", "allowMultipleCreditEntries", "allowMultipleDebitEntries", "tag")); - private final String resourceNameForPermission = "ACCOUNTINGRULE"; + private static final String RESOURCE_NAME_FOR_PERMISSION = "ACCOUNTINGRULE"; private final AccountingRuleReadPlatformService accountingRuleReadPlatformService; private final GLAccountReadPlatformService accountReadPlatformService; @@ -107,7 +107,7 @@ public class AccountingRuleApiResource { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.GetAccountRulesTemplateResponse.class))) }) public String retrieveTemplate(@Context final UriInfo uriInfo) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); AccountingRuleData accountingRuleData = null; accountingRuleData = handleTemplate(accountingRuleData); @@ -126,7 +126,7 @@ public String retrieveTemplate(@Context final UriInfo uriInfo) { public String retrieveAllAccountingRules(@Context final UriInfo uriInfo) { final AppUser currentUser = this.context.authenticatedUser(); - currentUser.validateHasReadPermission(this.resourceNameForPermission); + currentUser.validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchySearchString = hierarchy + "%"; @@ -159,7 +159,7 @@ public String retreiveAccountingRule( @PathParam("accountingRuleId") @Parameter(description = "accountingRuleId") final Long accountingRuleId, @Context final UriInfo uriInfo) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); AccountingRuleData accountingRuleData = this.accountingRuleReadPlatformService.retrieveAccountingRuleById(accountingRuleId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByQueryCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByQueryCommandStrategy.java index 75e71808e63..4bb42c1e851 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByQueryCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetDatatableEntryByQueryCommandStrategy.java @@ -23,6 +23,7 @@ import jakarta.ws.rs.core.UriInfo; import java.util.Map; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.batch.command.CommandStrategy; import org.apache.fineract.batch.command.CommandStrategyUtils; @@ -39,6 +40,7 @@ * raised by {@link DatatablesApiResource} and map those errors to appropriate status codes in BatchResponse. */ @Component +@Slf4j @RequiredArgsConstructor public class GetDatatableEntryByQueryCommandStrategy implements CommandStrategy { @@ -81,6 +83,7 @@ public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unus case "columnFilter" -> columnFilter = entry.getValue(); case "valueFilter" -> valueFilter = entry.getValue(); case "resultColumns" -> resultColumns = entry.getValue(); + default -> log.warn("Query parameter could not be mapped: {}", entry.getKey()); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java index d02179180a0..df2bc95a181 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java @@ -68,9 +68,12 @@ protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) { @OnReadError public void onReadError(Exception e) { - LoanReadException ee = (LoanReadException) e; - log.warn("Error was triggered during reading of Loan (id={}) due to: {}", ee.getId(), ThrowableSerialization.serialize(e)); - updateAccountLockWithError(List.of(ee.getId()), "Loan (id: %d) reading is failed", e); + if (e instanceof LoanReadException ee) { + log.warn("Error was triggered during reading of Loan (id={}) due to: {}", ee.getId(), ThrowableSerialization.serialize(e)); + updateAccountLockWithError(List.of(ee.getId()), "Loan (id: %d) reading is failed", e); + } else { + log.error("Could not handle read error", e); + } } @OnProcessError diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.java index c94ad88bdca..732af1ba345 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStep.java @@ -25,7 +25,6 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.domain.ActionContext; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -97,9 +96,9 @@ public Loan execute(Loan loan) { } private boolean isDelinquencyOnPause(Loan loan, List effectiveDelinquencyList) { - LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - boolean isPaused = isPausedOnDate(cobBusinessDate, effectiveDelinquencyList); - boolean wasPausedOneDayBefore = isPausedOnDate(cobBusinessDate.minusDays(1), effectiveDelinquencyList); + LocalDate businessDate = DateUtils.getBusinessLocalDate(); + boolean isPaused = isPausedOnDate(businessDate, effectiveDelinquencyList); + boolean wasPausedOneDayBefore = isPausedOnDate(businessDate.minusDays(1), effectiveDelinquencyList); if ((isPaused && !wasPausedOneDayBefore) || (!isPaused && wasPausedOneDayBefore)) { businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan)); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java index 07a427fc164..8643df3b606 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.cob.service; -import com.google.gson.Gson; import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; @@ -30,11 +29,8 @@ import org.apache.fineract.cob.loan.RetrieveLoanIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.domain.FineractContext; -import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Service; @@ -45,12 +41,8 @@ public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService { private final AsyncLoanCOBExecutorService asyncLoanCOBExecutorService; private final JobExecutionRepository jobExecutionRepository; - private final JobExplorer jobExplorer; private final RetrieveLoanIdService retrieveLoanIdService; - private final LoanAccountLockService accountLockService; - private final CustomJobParameterRepository customJobParameterRepository; - protected Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); @Override public void unlockHardLockedLoans() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java index 744b100106a..43486506c40 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java @@ -105,7 +105,7 @@ public String retrieveAuditEntries(@Context final UriInfo uriInfo, @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy, @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { - this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSIONS); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); final SQLBuilder extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo, checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId); @@ -135,7 +135,7 @@ public String retrieveAuditEntries(@Context final UriInfo uriInfo, public String retrieveAuditEntry(@PathParam("auditId") @Parameter(description = "auditId") final Long auditId, @Context final UriInfo uriInfo) { - this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSIONS); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final AuditData auditEntry = this.auditReadPlatformService.retrieveAuditEntry(auditId); @@ -153,7 +153,7 @@ public String retrieveAuditEntry(@PathParam("auditId") @Parameter(description = @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = MakercheckersApiResourceSwagger.GetMakerCheckersSearchTemplateResponse.class))) }) public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) { - this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSIONS); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java index 07c720d205d..9adaee5aea8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java @@ -21,6 +21,8 @@ import com.google.common.base.Splitter; import java.time.LocalDate; import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.EnumOptionData; @@ -40,6 +42,7 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellReference; +@Slf4j public final class ImportHandlerUtils { private ImportHandlerUtils() { @@ -73,20 +76,20 @@ public static Long readAsLong(int colIndex, Row row) { FormulaEvaluator eval = row.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator(); if (c.getCellType() == CellType.FORMULA) { if (eval != null) { - CellValue val = null; + CellValue value; try { - val = eval.evaluate(c); - } catch (NullPointerException npe) { - return null; + value = eval.evaluate(c); + return ((Double) value.getNumberValue()).longValue(); + } catch (Exception e) { + log.error("Cell evaluation error: ", e); } - return ((Double) val.getNumberValue()).longValue(); } + return null; } else if (c.getCellType() == CellType.NUMERIC) { return ((Double) c.getNumericCellValue()).longValue(); } else { return Long.parseLong(row.getCell(colIndex).getStringCellValue()); } - return null; } public static String readAsString(int colIndex, Row row) { @@ -98,26 +101,20 @@ public static String readAsString(int colIndex, Row row) { FormulaEvaluator eval = row.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator(); if (c.getCellType() == CellType.FORMULA) { if (eval != null) { - CellValue val = null; + CellValue value; try { - val = eval.evaluate(c); - } catch (NullPointerException npe) { - return null; - } + value = eval.evaluate(c); - String res = trimEmptyDecimalPortion(val.getStringValue()); - if (res != null) { - if (!res.equals("")) { + String res = trimEmptyDecimalPortion(value.getStringValue()); + + if (!StringUtils.isNotEmpty(res)) { return res.trim(); - } else { - return null; } - } else { - return null; + } catch (Exception e) { + log.error("Cell evaluation error: ", e); } - } else { - return null; } + return null; } else if (c.getCellType() == CellType.STRING) { String res = trimEmptyDecimalPortion(c.getStringCellValue().trim()); return res.trim(); @@ -156,13 +153,13 @@ public static Boolean readAsBoolean(int colIndex, Row row) { FormulaEvaluator eval = row.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator(); if (c.getCellType() == CellType.FORMULA) { if (eval != null) { - CellValue val = null; + CellValue value; try { - val = eval.evaluate(c); - } catch (NullPointerException npe) { - return false; + value = eval.evaluate(c); + return value.getBooleanValue(); + } catch (Exception e) { + log.error("Cell evaluation error: ", e); } - return val.getBooleanValue(); } return false; } else if (c.getCellType() == CellType.BOOLEAN) { @@ -185,13 +182,13 @@ public static Integer readAsInt(int colIndex, Row row) { FormulaEvaluator eval = row.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator(); if (c.getCellType() == CellType.FORMULA) { if (eval != null) { - CellValue val = null; + CellValue value; try { - val = eval.evaluate(c); - } catch (NullPointerException npe) { - return null; + value = eval.evaluate(c); + return ((Double) value.getNumberValue()).intValue(); + } catch (Exception e) { + log.error("Cell evaluation error: ", e); } - return ((Double) val.getNumberValue()).intValue(); } return null; } else if (c.getCellType() == CellType.NUMERIC) { @@ -209,16 +206,15 @@ public static Double readAsDouble(int colIndex, Row row) { FormulaEvaluator eval = row.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator(); if (c.getCellType() == CellType.FORMULA) { if (eval != null) { - CellValue val = null; + CellValue value; try { - val = eval.evaluate(c); - } catch (NullPointerException npe) { - return 0.0; + value = eval.evaluate(c); + return value.getNumberValue(); + } catch (Exception e) { + log.error("Cell evaluation error: ", e); } - return val.getNumberValue(); - } else { - return 0.0; } + return 0.0; } else if (c.getCellType() == CellType.NUMERIC) { return row.getCell(colIndex).getNumericCellValue(); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java index d375ad3dabb..42d16b395e1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java @@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.data.SavingsActivation; @@ -147,7 +148,9 @@ private SavingsAccountData readSavings(final Workbook workbook, final Row row, f Long interestPostingPeriodTypeId = null; EnumOptionData interestPostingPeriodTypeEnum = null; if (interestPostingPeriodType != null) { - if (interestPostingPeriodType.equalsIgnoreCase(MONTHLY)) { + if (interestPostingPeriodType.equalsIgnoreCase(DAILY)) { + interestPostingPeriodTypeId = SavingsPostingInterestPeriodType.DAILY.getValue().longValue(); + } else if (interestPostingPeriodType.equalsIgnoreCase(MONTHLY)) { interestPostingPeriodTypeId = 4L; } else if (interestPostingPeriodType.equalsIgnoreCase(QUARTERLY)) { interestPostingPeriodTypeId = 5L; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/CenterSheetPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/CenterSheetPopulator.java index d4064072c9d..d3dad4e89b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/CenterSheetPopulator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/CenterSheetPopulator.java @@ -58,7 +58,7 @@ public void populate(Workbook workbook, String dateFormat) { } private void centerNameToCenterIdMap() { - centerNameToCenterId = new HashMap(); + centerNameToCenterId = new HashMap<>(); for (CenterData centerData : allCenters) { centerNameToCenterId.put(centerData.getName(), centerData.getId()); } @@ -68,12 +68,12 @@ private void populateCentersByOfficeName(Sheet centerSheet) { int rowIndex = 1; int officeIndex = 0; int startIndex = 1; - officeNameToBeginEndIndexesOfCenters = new HashMap(); + officeNameToBeginEndIndexesOfCenters = new HashMap<>(); Row row = centerSheet.createRow(rowIndex); for (OfficeData office : offices) { startIndex = rowIndex + 1; writeString(OFFICE_NAME_COL, row, office.getName()); - ArrayList centersList = new ArrayList(); + ArrayList centersList; if (officeToCenters.containsKey(office.getName().trim().replaceAll("[ )(]", "_"))) { centersList = officeToCenters.get(office.getName().trim().replaceAll("[ )(]", "_")); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SavingsAccountSheetPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SavingsAccountSheetPopulator.java index 133fd508223..f33bdc3008c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SavingsAccountSheetPopulator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/SavingsAccountSheetPopulator.java @@ -19,9 +19,7 @@ package org.apache.fineract.infrastructure.bulkimport.populator; import java.util.List; -import java.util.Map; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; -import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; @@ -30,8 +28,6 @@ public class SavingsAccountSheetPopulator extends AbstractWorkbookPopulator { private List savingsAccountDataList; - private Map> clientToSavingsMap; - private static final int SAVINGS_ACCOUNT_ID_COL = 0; private static final int SAVING_ACCOUNT_NO = 1; private static final int CURRENCY_COL = 2; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/comparator/LoanComparatorByStatusActive.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/comparator/LoanComparatorByStatusActive.java index 5bfc7721c7d..4a29c916b56 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/comparator/LoanComparatorByStatusActive.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/comparator/LoanComparatorByStatusActive.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.infrastructure.bulkimport.populator.comparator; +import java.io.Serializable; import java.util.Comparator; import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; @@ -25,7 +26,7 @@ * Sorting the loan values based on loan status giving priority to active loans */ -public class LoanComparatorByStatusActive implements Comparator { +public class LoanComparatorByStatusActive implements Comparator, Serializable { @Override public int compare(LoanAccountData o1, LoanAccountData o2) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java index 13e765c68dd..fffe54ad2c9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java @@ -41,19 +41,20 @@ public String getCode() { } public static CampaignType fromInt(final Integer typeValue) { - CampaignType type = null; + if (typeValue == null) { + return INVALID; + } + switch (typeValue) { case 0: - type = INVALID; - break; + return INVALID; case 1: - type = SMS; - break; + return SMS; case 2: - type = NOTIFICATION; - break; + return NOTIFICATION; + default: + return INVALID; } - return type; } public static EnumOptionData campaignType(final Integer campaignTypeId) { @@ -61,28 +62,22 @@ public static EnumOptionData campaignType(final Integer campaignTypeId) { } public static EnumOptionData campaignType(final CampaignType campaignType) { - EnumOptionData optionData = new EnumOptionData(CampaignType.INVALID.getValue().longValue(), CampaignType.INVALID.getCode(), - "Invalid"); + EnumOptionData optionData = new EnumOptionData(INVALID.getValue().longValue(), INVALID.getCode(), "Invalid"); switch (campaignType) { case INVALID: - optionData = new EnumOptionData(CampaignType.INVALID.getValue().longValue(), CampaignType.INVALID.getCode(), "Invalid"); + optionData = new EnumOptionData(INVALID.getValue().longValue(), INVALID.getCode(), "Invalid"); break; case SMS: - optionData = new EnumOptionData(CampaignType.SMS.getValue().longValue(), CampaignType.SMS.getCode(), "SMS"); + optionData = new EnumOptionData(SMS.getValue().longValue(), SMS.getCode(), "SMS"); break; case NOTIFICATION: - optionData = new EnumOptionData(CampaignType.NOTIFICATION.getValue().longValue(), CampaignType.NOTIFICATION.getCode(), - "NOTIFICATION"); + optionData = new EnumOptionData(NOTIFICATION.getValue().longValue(), NOTIFICATION.getCode(), "NOTIFICATION"); break; } return optionData; } public boolean isSms() { - return this.value.equals(CampaignType.SMS.getValue()); - } - - public boolean isNotificaion() { - return this.value.equals(CampaignType.NOTIFICATION.getValue()); + return this.equals(SMS); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java index 0ef6b2bd06f..ce0aee75c67 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java @@ -30,7 +30,6 @@ import org.apache.fineract.infrastructure.campaigns.email.data.EmailBusinessRulesData; import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignData; import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignTimeLine; -import org.apache.fineract.infrastructure.campaigns.email.data.ScheduledEmailEnumerations; import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignStatus; import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignStatusEnumerations; import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignType; @@ -116,24 +115,7 @@ public EmailCampaignData mapRow(ResultSet rs, int rowNum) throws SQLException { final String emailMessage = rs.getString("emailMessage"); final String emailAttachmentFileFormatString = rs.getString("emailAttachmentFileFormat"); final String stretchyReportParamMap = rs.getString("stretchyReportParamMap"); - EnumOptionData emailAttachmentFileFormat = null; - if (emailAttachmentFileFormatString != null) { - emailAttachmentFileFormat = ScheduledEmailEnumerations.emailAttachementFileFormat(emailAttachmentFileFormatString); - } final Long reportId = JdbcSupport.getLong(rs, "stretchyReportId"); - final String reportName = rs.getString("reportName"); - final String reportType = rs.getString("reportType"); - final String reportSubType = rs.getString("reportSubType"); - final String reportCategory = rs.getString("reportCategory"); - final String reportSql = rs.getString("reportSql"); - final String reportDescription = rs.getString("reportDescription"); - final boolean coreReport = rs.getBoolean("coreReport"); - final boolean useReport = rs.getBoolean("useReport"); - - /* - * final ReportData stretchyReport = new ReportData(reportId, reportName, reportType, reportSubType, - * reportCategory, reportDescription, reportSql, coreReport, useReport, null); - */ final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum"); final EnumOptionData status = EmailCampaignStatusEnumerations.status(statusId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java index 3b206bc75cb..ba3e487a412 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java @@ -350,11 +350,11 @@ public List> getRunReportByServiceImpl(final String repo throws IOException { final String reportType = "report"; - List> resultList = new ArrayList>(); + List> resultList; final GenericResultsetData results = this.readReportingService.retrieveGenericResultSetForSmsEmailCampaign(reportName, reportType, queryParams); final String response = this.genericDataService.generateJsonFromGenericResultsetData(results); - resultList = new ObjectMapper().readValue(response, new TypeReference>>() {}); + resultList = new ObjectMapper().readValue(response, new TypeReference<>() {}); // loop changes array date to string date for (Iterator> it = resultList.iterator(); it.hasNext();) { HashMap entry = it.next(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java index 21f7a6cf511..9409e997205 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java @@ -30,7 +30,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.stereotype.Service; @Service @@ -43,8 +42,9 @@ public class EmailConfigurationWritePlatformServiceImpl implements EmailConfigur @Override public CommandProcessingResult update(final JsonCommand command) { - - final AppUser currentUser = this.context.authenticatedUser(); + // TODO: leaving function call for backward compatibility... but security configuration should be done somewhere + // else + this.context.authenticatedUser(); this.emailConfigurationValidator.validateUpdateConfiguration(command.json()); final String smtpUsername = command.stringValueOfParameterNamed("SMTP_USERNAME"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executeemail/ExecuteEmailTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executeemail/ExecuteEmailTasklet.java index 069afb9ca47..90168797c85 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executeemail/ExecuteEmailTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/jobs/executeemail/ExecuteEmailTasklet.java @@ -193,6 +193,8 @@ private HashMap replaceStretchyParamsWithActualClientParams(fina case "environementUrl": actualParams.put(entry.getKey(), entry.getKey()); break; + default: + log.warn("Query parameter could not be mapped: {}", entry.getKey()); } } return actualParams; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java index 399e90ac680..5f02b2a8b93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java @@ -34,20 +34,16 @@ public enum SmsCampaignStatus { } public static SmsCampaignStatus fromInt(final Integer statusValue) { - - SmsCampaignStatus enumeration = SmsCampaignStatus.INVALID; switch (statusValue) { case 100: - enumeration = SmsCampaignStatus.PENDING; - break; + return PENDING; case 300: - enumeration = SmsCampaignStatus.ACTIVE; - break; + return ACTIVE; case 600: - enumeration = SmsCampaignStatus.CLOSED; - break; + return CLOSED; + default: + return INVALID; } - return enumeration; } public Integer getValue() { @@ -58,15 +54,18 @@ public String getCode() { return code; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isActive() { - return this.value.equals(SmsCampaignStatus.ACTIVE.getValue()); + return this.equals(ACTIVE); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isPending() { - return this.value.equals(SmsCampaignStatus.PENDING.getValue()); + return this.equals(PENDING); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isClosed() { - return this.value.equals(SmsCampaignStatus.CLOSED.getValue()); + return this.equals(CLOSED); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java index 83349e9bb37..7f4cdef79f9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java @@ -41,20 +41,21 @@ public String getCode() { return code; } - public static SmsCampaignTriggerType fromInt(final Integer typeValue) { - SmsCampaignTriggerType type = null; - switch (typeValue) { + public static SmsCampaignTriggerType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { case 1: - type = DIRECT; - break; + return DIRECT; case 2: - type = SCHEDULE; - break; + return SCHEDULE; case 3: - type = TRIGGERED; - break; + return TRIGGERED; + default: + return INVALID; } - return type; } public static EnumOptionData triggerType(final Integer triggerTypeId) { @@ -62,38 +63,32 @@ public static EnumOptionData triggerType(final Integer triggerTypeId) { } public static EnumOptionData triggerType(final SmsCampaignTriggerType triggerType) { - EnumOptionData optionData = new EnumOptionData(SmsCampaignTriggerType.INVALID.getValue().longValue(), - SmsCampaignTriggerType.INVALID.getCode(), "Invalid"); switch (triggerType) { case INVALID: - optionData = new EnumOptionData(SmsCampaignTriggerType.INVALID.getValue().longValue(), - SmsCampaignTriggerType.INVALID.getCode(), "Invalid"); - break; + return new EnumOptionData(INVALID.getValue().longValue(), INVALID.getCode(), "Invalid"); case DIRECT: - optionData = new EnumOptionData(SmsCampaignTriggerType.DIRECT.getValue().longValue(), - SmsCampaignTriggerType.DIRECT.getCode(), "Direct"); - break; + return new EnumOptionData(DIRECT.getValue().longValue(), DIRECT.getCode(), "Direct"); case SCHEDULE: - optionData = new EnumOptionData(SmsCampaignTriggerType.SCHEDULE.getValue().longValue(), - SmsCampaignTriggerType.SCHEDULE.getCode(), "Schedule"); - break; + return new EnumOptionData(SCHEDULE.getValue().longValue(), SCHEDULE.getCode(), "Schedule"); case TRIGGERED: - optionData = new EnumOptionData(SmsCampaignTriggerType.TRIGGERED.getValue().longValue(), - SmsCampaignTriggerType.TRIGGERED.getCode(), "Triggered"); - break; + return new EnumOptionData(TRIGGERED.getValue().longValue(), TRIGGERED.getCode(), "Triggered"); + default: + return new EnumOptionData(INVALID.getValue().longValue(), INVALID.getCode(), "Invalid"); } - return optionData; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isDirect() { - return this.value.equals(SmsCampaignTriggerType.DIRECT.getValue()); + return this.equals(DIRECT); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isSchedule() { - return this.value.equals(SmsCampaignTriggerType.SCHEDULE.getValue()); + return this.equals(SCHEDULE); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isTriggered() { - return this.value.equals(SmsCampaignTriggerType.TRIGGERED.getValue()); + return this.equals(TRIGGERED); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java index 62debdffeb9..2cdf3d2652e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java @@ -183,18 +183,18 @@ private void sendSmsForLoanRepayment(LoanTransaction loanTransaction) { }); - if (groupClients.size() > 0) { + if (!groupClients.isEmpty()) { for (Client client : groupClients) { HashMap smsParams = processRepaymentDataForSms(loanTransaction, client); - for (String key : campaignParams.keySet()) { - String value = campaignParams.get(key); + for (Map.Entry entry : campaignParams.entrySet()) { + String value = entry.getValue(); String spvalue = null; - boolean spkeycheck = smsParams.containsKey(key); + boolean spkeycheck = smsParams.containsKey(entry.getKey()); if (spkeycheck) { - spvalue = smsParams.get(key).toString(); + spvalue = smsParams.get(entry.getKey()).toString(); } if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { - if (key.equals("officeId")) { + if (entry.getKey().equals("officeId")) { Long officeId = Long.valueOf(value); Office campaignOffice = this.officeRepository.findById(Long.valueOf(value)) .orElseThrow(() -> new OfficeNotFoundException(officeId)); @@ -244,15 +244,15 @@ private void sendSmsForSavingsTransaction(final SavingsAccountTransaction saving }); HashMap smsParams = processSavingsTransactionDataForSms(savingsTransaction, client); - for (String key : campaignParams.keySet()) { - String value = campaignParams.get(key); + for (Map.Entry entry : campaignParams.entrySet()) { + String value = entry.getValue(); String spvalue = null; - boolean spkeycheck = smsParams.containsKey(key); + boolean spkeycheck = smsParams.containsKey(entry.getKey()); if (spkeycheck) { - spvalue = smsParams.get(key).toString(); + spvalue = smsParams.get(entry.getKey()).toString(); } if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { - if (key.equals("officeId")) { + if (entry.getKey().equals("officeId")) { Long officeId = Long.valueOf(value); Office campaignOffice = this.officeRepository.findById(officeId) .orElseThrow(() -> new OfficeNotFoundException(officeId)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java index 61e5df6d2bf..c333a4203da 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java @@ -19,7 +19,6 @@ package org.apache.fineract.infrastructure.campaigns.sms.service; import java.net.URI; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -72,7 +71,7 @@ public Collection retrieveCampaignTriggerTypes() { @Override public Collection retrieveSmsProviders() { - Collection smsProviderOptions = new ArrayList<>(); + Collection smsProviderOptions; Map hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("smsbridges", null); URI uri = (URI) hostConfig.get("uri"); HttpEntity entity = (HttpEntity) hostConfig.get("entity"); @@ -80,8 +79,7 @@ public Collection retrieveSmsProviders() { ResponseEntity> responseOne = null; try { - responseOne = restTemplate.exchange(uri, HttpMethod.GET, entity, - new ParameterizedTypeReference>() {}); + responseOne = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<>() {}); } catch (ResourceAccessException ex) { LOG.debug("Mobile service provider {} not available", uri, ex); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index b176a2a415e..3cb8a4b6b8f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.infrastructure.configuration.domain; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -74,6 +75,11 @@ public boolean isMakerCheckerEnabledForTask(final String taskPermissionCode) { return false; } + @Override + public boolean isSameMakerCheckerEnabled() { + return getGlobalConfigurationPropertyData("enable-same-maker-checker").isEnabled(); + } + @Override public boolean isAmazonS3Enabled() { return getGlobalConfigurationPropertyData("amazon-S3").isEnabled(); @@ -379,18 +385,14 @@ public Long retrieveRelaxingDaysConfigForPivotDate() { return property.getValue(); } + @NotNull private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) { return globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName).toData(); } @Override public boolean isSubRatesEnabled() { - GlobalConfigurationPropertyData configuration = getGlobalConfigurationPropertyData("sub-rates"); - if (configuration == null) { - return false; - } else { - return configuration.isEnabled(); - } + return getGlobalConfigurationPropertyData("sub-rates").isEnabled(); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OkHttp3Config.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OkHttp3Config.java new file mode 100644 index 00000000000..eb5d81c1b22 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OkHttp3Config.java @@ -0,0 +1,71 @@ +/** + * 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. + */ + +package org.apache.fineract.infrastructure.core.config; + +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@RequiredArgsConstructor +@Configuration +public class OkHttp3Config { + + private final FineractProperties fineractProperties; + + @Bean + public OkHttpClient okHttpClient() throws Exception { + var okBuilder = new OkHttpClient.Builder(); + + if (Boolean.TRUE.equals(fineractProperties.getInsecureHttpClient())) { + final X509TrustManager insecureX509TrustManager = new X509TrustManager() { + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}// NOSONAR + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}// NOSONAR + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + }; + + SSLContext insecureSSLContext = SSLContext.getInstance("TLS"); + insecureSSLContext.init(null, new TrustManager[] { insecureX509TrustManager }, new SecureRandom()); + + okBuilder.sslSocketFactory(insecureSSLContext.getSocketFactory(), insecureX509TrustManager); + HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true;// NOSONAR + okBuilder.hostnameVerifier(insecureHostnameVerifier); + } + + return okBuilder.build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java index e4202ae3479..216328bb854 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java @@ -61,25 +61,53 @@ private CacheManager getInternalEhCacheManager() { CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(10000)) .withExpiry(ExpiryPolicyBuilder.noExpiration()).build()); - cacheManager.createCache("users", defaultTemplate); - cacheManager.createCache("usersByUsername", defaultTemplate); - cacheManager.createCache("tenantsById", defaultTemplate); - cacheManager.createCache("offices", defaultTemplate); - cacheManager.createCache("officesForDropdown", defaultTemplate); - cacheManager.createCache("officesById", defaultTemplate); - cacheManager.createCache("charges", defaultTemplate); - cacheManager.createCache("funds", defaultTemplate); - cacheManager.createCache("code_values", defaultTemplate); - cacheManager.createCache("codes", defaultTemplate); - cacheManager.createCache("hooks", defaultTemplate); - cacheManager.createCache("tfConfig", defaultTemplate); - cacheManager.createCache(CONFIG_BY_NAME_CACHE_NAME, defaultTemplate); + if (cacheManager.getCache("users") == null) { + cacheManager.createCache("users", defaultTemplate); + } + if (cacheManager.getCache("usersByUsername") == null) { + cacheManager.createCache("usersByUsername", defaultTemplate); + } + if (cacheManager.getCache("tenantsById") == null) { + cacheManager.createCache("tenantsById", defaultTemplate); + } + if (cacheManager.getCache("offices") == null) { + cacheManager.createCache("offices", defaultTemplate); + } + if (cacheManager.getCache("officesForDropdown") == null) { + cacheManager.createCache("officesForDropdown", defaultTemplate); + } + if (cacheManager.getCache("officesById") == null) { + cacheManager.createCache("officesById", defaultTemplate); + } + if (cacheManager.getCache("charges") == null) { + cacheManager.createCache("charges", defaultTemplate); + } + if (cacheManager.getCache("funds") == null) { + cacheManager.createCache("funds", defaultTemplate); + } + if (cacheManager.getCache("code_values") == null) { + cacheManager.createCache("code_values", defaultTemplate); + } + if (cacheManager.getCache("codes") == null) { + cacheManager.createCache("codes", defaultTemplate); + } + if (cacheManager.getCache("hooks") == null) { + cacheManager.createCache("hooks", defaultTemplate); + } + if (cacheManager.getCache("tfConfig") == null) { + cacheManager.createCache("tfConfig", defaultTemplate); + } + if (cacheManager.getCache(CONFIG_BY_NAME_CACHE_NAME) == null) { + cacheManager.createCache(CONFIG_BY_NAME_CACHE_NAME, defaultTemplate); + } javax.cache.configuration.Configuration accessTokenTemplate = Eh107Configuration.fromEhcacheCacheConfiguration( CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(10000)) .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofHours(2))).build()); - cacheManager.createCache("userTFAccessToken", accessTokenTemplate); + if (cacheManager.getCache("userTFAccessToken") == null) { + cacheManager.createCache("userTFAccessToken", accessTokenTemplate); + } return cacheManager; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java index e8d693fd80d..63822ce3a73 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java @@ -21,6 +21,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.sql.ResultSet; import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import liquibase.change.custom.CustomTaskChange; import liquibase.database.Database; import liquibase.database.jvm.JdbcConnection; @@ -42,6 +44,9 @@ public class TenantPasswordEncryptionTask implements CustomTaskChange, Applicati private static DatabasePasswordEncryptor databasePasswordEncryptor; + // NOTE: workaround for double execution bug; see: https://github.com/liquibase/liquibase/issues/3945 + private Map done = new ConcurrentHashMap<>(); + @Override public void execute(Database database) throws CustomChangeException { JdbcConnection dbConn = (JdbcConnection) database.getConnection(); // autocommit is false @@ -50,16 +55,18 @@ public void execute(Database database) throws CustomChangeException { try (ResultSet rs = selectStatement.executeQuery("SELECT id, schema_password FROM tenant_server_connections")) { while (rs.next()) { String id = rs.getString("id"); - String schemaPassword = rs.getString("schema_password"); - String encryptedPassword = TenantPasswordEncryptionTask.databasePasswordEncryptor.encrypt(schemaPassword); + if (!Boolean.TRUE.equals(done.get(id))) { + String schemaPassword = rs.getString("schema_password"); + String encryptedPassword = TenantPasswordEncryptionTask.databasePasswordEncryptor.encrypt(schemaPassword); - String updateSql = String.format( - "update tenant_server_connections set schema_password = '%s', master_password_hash = '%s' where id = %s", - encryptedPassword, TenantPasswordEncryptionTask.databasePasswordEncryptor.getMasterPasswordHash(), id); - updateStatement.execute(updateSql); + String updateSql = String.format( + "update tenant_server_connections set schema_password = '%s', master_password_hash = '%s' where id = %s", + encryptedPassword, TenantPasswordEncryptionTask.databasePasswordEncryptor.getMasterPasswordHash(), id); + updateStatement.execute(updateSql); + done.put(id, true); + } } } - } catch (Exception e) { throw new CustomChangeException(e); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java index e019ae163d6..a3537ae9fe9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java @@ -21,6 +21,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.sql.ResultSet; import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import liquibase.change.custom.CustomTaskChange; import liquibase.database.Database; import liquibase.database.jvm.JdbcConnection; @@ -42,6 +44,9 @@ public class TenantReadOnlyPasswordEncryptionTask implements CustomTaskChange, A private static DatabasePasswordEncryptor databasePasswordEncryptor; + // NOTE: workaround for double execution bug; see: https://github.com/liquibase/liquibase/issues/3945 + private Map done = new ConcurrentHashMap<>(); + @Override public void execute(Database database) throws CustomChangeException { JdbcConnection dbConn = (JdbcConnection) database.getConnection(); // autocommit is false @@ -51,17 +56,20 @@ public void execute(Database database) throws CustomChangeException { "SELECT id, readonly_schema_password FROM tenant_server_connections WHERE readonly_schema_password IS NOT NULL")) { while (rs.next()) { String id = rs.getString("id"); - String readOnlySchemaPassword = rs.getString("readonly_schema_password"); - String encryptedPassword = TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor - .encrypt(readOnlySchemaPassword); + if (!Boolean.TRUE.equals(done.get(id))) { + String readOnlySchemaPassword = rs.getString("readonly_schema_password"); + String encryptedPassword = TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor + .encrypt(readOnlySchemaPassword); - String updateSql = String.format( - "update tenant_server_connections set readonly_schema_password = '%s', master_password_hash = '%s' where id = %s", - encryptedPassword, TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor.getMasterPasswordHash(), id); - updateStatement.execute(updateSql); + String updateSql = String.format( + "update tenant_server_connections set readonly_schema_password = '%s', master_password_hash = '%s' where id = %s", + encryptedPassword, TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor.getMasterPasswordHash(), + id); + updateStatement.execute(updateSql); + done.put(id, true); + } } } - } catch (Exception e) { throw new CustomChangeException(e); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java index 888a8d0a6d7..8316d71bd79 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java @@ -39,6 +39,8 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.MultipartBody; @@ -65,18 +67,14 @@ import org.apache.fineract.infrastructure.creditbureau.serialization.CreditBureauTokenCommandFromApiJsonDeserializer; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Component +@Slf4j +@RequiredArgsConstructor @Service public class ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl implements ThitsaWorksCreditBureauIntegrationWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.class); public static final String UPLOAD_CREDIT_REPORT = "UploadCreditReport"; public static final String RESPONSE_MESSAGE = "ResponseMessage"; public static final String IS_NOT_AVAILABLE_SUFFIX = ".is.not.available"; @@ -85,29 +83,8 @@ public class ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl implemen private final TokenRepositoryWrapper tokenRepositoryWrapper; private final CreditBureauConfigurationRepositoryWrapper configDataRepository; private final CreditBureauTokenCommandFromApiJsonDeserializer fromApiJsonDeserializer; - private final OkHttpClient client; - @Autowired - public ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl(final PlatformSecurityContext context, - final FromJsonHelper fromApiJsonHelper, final TokenRepositoryWrapper tokenRepositoryWrapper, - final CreditBureauConfigurationRepositoryWrapper configDataRepository, - final CreditBureauTokenCommandFromApiJsonDeserializer fromApiJsonDeserializer) { - this(new OkHttpClient(), context, fromApiJsonHelper, tokenRepositoryWrapper, configDataRepository, fromApiJsonDeserializer); - } - - public ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl(final OkHttpClient okHttpClient, - final PlatformSecurityContext context, final FromJsonHelper fromApiJsonHelper, - final TokenRepositoryWrapper tokenRepositoryWrapper, final CreditBureauConfigurationRepositoryWrapper configDataRepository, - final CreditBureauTokenCommandFromApiJsonDeserializer fromApiJsonDeserializer) { - this.client = okHttpClient; - this.context = context; - this.tokenRepositoryWrapper = tokenRepositoryWrapper; - this.configDataRepository = configDataRepository; - this.fromApiJsonHelper = fromApiJsonHelper; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; - } - @Transactional @Override public String okHttpConnectionMethod(String userName, String password, String subscriptionKey, String subscriptionId, String url, @@ -151,7 +128,7 @@ public String okHttpConnectionMethod(String userName, String password, String su responseMessage = response.body().string(); } catch (IOException e) { - LOG.error("error occured in HTTP request-response method.", e); + log.error("error occured in HTTP request-response method.", e); } if (responseCode != HttpURLConnection.HTTP_OK) { @@ -494,7 +471,7 @@ public String getCreditBureauConfiguration(Integer creditBureauId, String config "creditBureau.Configuration." + configurationParameterName + IS_NOT_AVAILABLE_SUFFIX); } - } catch (NullPointerException ex) { + } catch (Exception ex) { baseDataValidator.reset().failWithCode("creditBureau.configuration.is.not.available"); throw new PlatformApiDataValidationException("creditBureau.Configuration.is.not.available" + ex, "creditBureau.Configuration.is.not.available", dataValidationErrors); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java index d116e1a15d7..b845b222469 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java @@ -37,7 +37,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.csv.CSVFormat; @@ -133,11 +132,8 @@ private String getSQLtoRun(final String name, final String type, final Map keys = queryParams.keySet(); - for (final String key : keys) { - final String pValue = queryParams.get(key); - // LOG.info("({} : {})", key, pValue); - sql = this.genericDataService.replace(sql, key, pValue); + for (Map.Entry entry : queryParams.entrySet()) { + sql = this.genericDataService.replace(sql, entry.getKey(), entry.getValue()); } final AppUser currentUser = this.context.authenticatedUser(); @@ -238,14 +234,9 @@ public String retrieveReportPDF(final String reportName, final String type, fina row = element.getRow(); rSize = row.size(); for (int j = 0; j < rSize; j++) { - currColType = columnHeaders.get(j).getColumnType(); currVal = (String) row.get(j); if (currVal != null) { - if (currColType.isNumericType()) { - table.addCell(currVal.toString()); - } else { - table.addCell(currVal.toString()); - } + table.addCell(currVal); } } } @@ -453,11 +444,8 @@ public GenericResultsetData retrieveGenericResultSetForSmsEmailCampaign(String n private String sqlToRunForSmsEmailCampaign(final String name, final String type, final Map queryParams) { String sql = getSql(name, type); - final Set keys = queryParams.keySet(); - for (String key : keys) { - final String pValue = queryParams.get(key); - key = "${" + key + "}"; - sql = this.genericDataService.replace(sql, key, pValue); + for (Map.Entry entry : queryParams.entrySet()) { + sql = this.genericDataService.replace(sql, "${" + entry.getKey() + "}", entry.getValue()); } sql = this.genericDataService.wrapSQL(sql); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index 022ad5ee603..fd1b77fd341 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -219,7 +219,7 @@ public List queryDataTable(@NotNull String datatable, @NotNull Strin Object columnValue = SearchUtil.parseJdbcColumnValue(column, columnValueString, null, null, null, false, sqlGenerator); String sql = sqlGenerator.buildSelect(selectColumns, null, false) + " " + sqlGenerator.buildFrom(datatable, null, false) + " WHERE " + EQ.formatPlaceholder(sqlGenerator, column.getColumnName(), 1, null); - SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, columnValue); + SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, columnValue); // NOSONAR List results = new ArrayList<>(); while (rowSet.next()) { @@ -279,7 +279,7 @@ public Page queryDataTableAdvanced(@NotNull String datatable, @NotNu // Execute the count Query String countQuery = "SELECT COUNT(*)" + from + where; - Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); + Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); // NOSONAR if (totalElements == null || totalElements == 0) { return PageableExecutionUtils.getPage(results, pageable, () -> 0); } @@ -371,7 +371,7 @@ public void registerDatatable(final JsonCommand command, final String permission private void registerDataTable(final String entityName, final String dataTableName, final String entitySubType, final Integer category, final String permissionsSql) { - EntityTables entityTable = resolveEntity(entityName); + resolveEntity(entityName); validateDatatableName(dataTableName); validateDataTableExists(dataTableName); @@ -1012,7 +1012,7 @@ private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder String findFKSql = "SELECT count(*) FROM information_schema.TABLE_CONSTRAINTS i" + " WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND " + schemaSql + " AND i.TABLE_NAME = '" + datatableName + "' AND i.CONSTRAINT_NAME = '" + fkName + "' "; - final Integer count = this.jdbcTemplate.queryForObject(findFKSql, Integer.class); + final Integer count = this.jdbcTemplate.queryForObject(findFKSql, Integer.class); // NOSONAR if (count != null && count > 0) { codeMappings.add(datatableAlias + "_" + name); constrainBuilder.append(", DROP FOREIGN KEY ").append(sqlGenerator.escape(fkName)).append(" "); @@ -1076,7 +1076,7 @@ private void removeNullValuesFromStringColumn(final String datatableName, final if (type != null && mandatory && type.isStringType()) { String sql = "UPDATE " + sqlGenerator.escape(datatableName) + " SET " + sqlGenerator.escape(name) + " = '' WHERE " + sqlGenerator.escape(name) + " IS NULL"; - this.jdbcTemplate.update(sql); + this.jdbcTemplate.update(sql); // NOSONAR } } @@ -1141,12 +1141,12 @@ private void checkColumnRenameAndModifyUniqueConstraint(String datatableName, St private void createUniqueConstraint(String datatableName, String columnName, String uniqueKeyName) { String sql = "ALTER TABLE " + sqlGenerator.escape(datatableName) + " ADD CONSTRAINT " + sqlGenerator.escape(uniqueKeyName) + " UNIQUE (" + sqlGenerator.escape(columnName) + ");"; - this.jdbcTemplate.execute(sql); + this.jdbcTemplate.execute(sql); // NOSONAR } private void dropUniqueConstraint(String datatableName, String uniqueKeyName) { String sql = "ALTER TABLE " + sqlGenerator.escape(datatableName) + " DROP CONSTRAINT " + sqlGenerator.escape(uniqueKeyName) + ";"; - this.jdbcTemplate.execute(sql); + this.jdbcTemplate.execute(sql); // NOSONAR } private void updateIndexesForTable(String datatableName, JsonArray changeColumns, @@ -1294,16 +1294,16 @@ private CommandProcessingResult createNewDatatableEntry(final String dataTableNa List.of(entityTable.getForeignKeyColumnNameOnDatatable(), CREATEDAT_FIELD_NAME, UPDATEDAT_FIELD_NAME)); LocalDateTime auditDateTime = DateUtils.getAuditLocalDateTime(); ArrayList params = new ArrayList<>(List.of(appTableId, auditDateTime, auditDateTime)); - for (String key : dataParams.keySet()) { - if (isTechnicalParam(key)) { + for (Map.Entry entry : dataParams.entrySet()) { + if (isTechnicalParam(entry.getKey())) { continue; } - ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(key, headersByName, false); + ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false); if (!isUserInsertable(entityTable, columnHeader)) { continue; } insertColumns.add(columnHeader.getColumnName()); - params.add(SearchUtil.parseJdbcColumnValue(columnHeader, dataParams.get(key), dateFormat, dateTimeFormat, locale, false, + params.add(SearchUtil.parseJdbcColumnValue(columnHeader, entry.getValue(), dateFormat, dateTimeFormat, locale, false, sqlGenerator)); } if (addScore) { @@ -1403,17 +1403,17 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, ArrayList updateColumns = new ArrayList<>(List.of(UPDATEDAT_FIELD_NAME)); ArrayList params = new ArrayList<>(List.of(DateUtils.getAuditLocalDateTime())); final HashMap changes = new HashMap<>(); - for (String key : dataParams.keySet()) { - if (isTechnicalParam(key)) { + for (Map.Entry entry : dataParams.entrySet()) { + if (isTechnicalParam(entry.getKey())) { continue; } - ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(key, headersByName, false); + ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false); if (!isUserUpdatable(entityTable, columnHeader)) { continue; } String columnName = columnHeader.getColumnName(); Object existingValue = valuesByHeader.get(columnHeader); - Object columnValue = SearchUtil.parseColumnValue(columnHeader, dataParams.get(key), dateFormat, dateTimeFormat, locale, false, + Object columnValue = SearchUtil.parseColumnValue(columnHeader, entry.getValue(), dateFormat, dateTimeFormat, locale, false, sqlGenerator); if ((columnHeader.getColumnType().isDecimalType() && MathUtil.isEqualTo((BigDecimal) existingValue, (BigDecimal) columnValue)) || (existingValue == null ? columnValue == null : existingValue.equals(columnValue))) { @@ -1430,7 +1430,7 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, params.add(primaryKey); final String sql = sqlGenerator.buildUpdate(dataTableName, updateColumns, headersByName) + " WHERE " + pkColumn.getColumnName() + " = ?"; - int updated = jdbcTemplate.update(sql, params.toArray(Object[]::new)); + int updated = jdbcTemplate.update(sql, params.toArray(Object[]::new)); // NOSONAR if (updated != 1) { throw new PlatformDataIntegrityException("error.msg.invalid.update", "Expected one updated row."); } @@ -1486,7 +1486,7 @@ private CommandProcessingResult deleteDatatableEntries(final String dataTableNam String sql = "DELETE FROM " + sqlGenerator.escape(dataTableName) + " WHERE " + sqlGenerator.escape(whereColumn) + " = " + whereValue; - this.jdbcTemplate.update(sql); + this.jdbcTemplate.update(sql); // NOSONAR return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(whereValue) // @@ -1643,7 +1643,7 @@ public boolean isDatatableAttachedToEntityDatatableCheck(final String datatableN String sql = "SELECT COUNT(edc.x_registered_table_name) FROM x_registered_table xrt" + " JOIN m_entity_datatable_check edc ON edc.x_registered_table_name = xrt.registered_table_name" + " WHERE edc.x_registered_table_name = '" + datatableName + "'"; - final Long count = this.jdbcTemplate.queryForObject(sql, Long.class); + final Long count = this.jdbcTemplate.queryForObject(sql, Long.class); // NOSONAR return count != null && count > 0; } @@ -1682,7 +1682,7 @@ private boolean isRegisteredDataTable(final String datatable) { private void validateDataTableExists(final String datatableName) { final String sql = "select (CASE WHEN exists (select 1 from information_schema.tables where table_schema = " + sqlGenerator.currentSchema() + " and table_name = ?) THEN 'true' ELSE 'false' END)"; - final boolean dataTableExists = Boolean.parseBoolean(this.jdbcTemplate.queryForObject(sql, String.class, datatableName)); + final boolean dataTableExists = Boolean.parseBoolean(this.jdbcTemplate.queryForObject(sql, String.class, datatableName)); // NOSONAR if (!dataTableExists) { throw new PlatformDataIntegrityException("error.msg.invalid.datatable", "Invalid Data Table: " + datatableName, API_FIELD_NAME, datatableName); @@ -1716,7 +1716,7 @@ private String mapApiTypeToDbType(String apiType, Integer length) { private int getDatatableRowCount(final String datatableName) { final String sql = "select count(*) from " + sqlGenerator.escape(datatableName); - Integer count = this.jdbcTemplate.queryForObject(sql, Integer.class); + Integer count = this.jdbcTemplate.queryForObject(sql, Integer.class); // NOSONAR return count == null ? 0 : count; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java index 21035ca3c66..09f75bcac71 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java @@ -134,15 +134,13 @@ private Object deletePreviousImage(String entityName, final Long entityId) { private CommandProcessingResult updateImage(final Object owner, final String imageLocation, final StorageType storageType) { Image image = null; Long clientId = null; - if (owner instanceof Client) { - Client client = (Client) owner; + if (owner instanceof Client client) { image = client.getImage(); clientId = client.getId(); image = createImage(image, imageLocation, storageType); client.setImage(image); this.clientRepositoryWrapper.save(client); - } else if (owner instanceof Staff) { - Staff staff = (Staff) owner; + } else if (owner instanceof Staff staff) { image = staff.getImage(); clientId = staff.getId(); image = createImage(image, imageLocation, storageType); @@ -150,7 +148,9 @@ private CommandProcessingResult updateImage(final Object owner, final String ima this.staffRepositoryWrapper.save(staff); } - this.imageRepository.save(image); + if (image != null) { + this.imageRepository.save(image); + } return CommandProcessingResult.resourceResult(clientId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducer.java index 9cf815e4182..952a7b552ab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducer.java @@ -58,10 +58,9 @@ public void sendEvents(Map> partitions) throws Acknowledgemen List>> sendResults = new ArrayList<>(); measure(() -> { Set keys = partitions.keySet(); - for (Long key : keys) { - List messages = partitions.get(key); - for (byte[] message : messages) { - sendResults.add(externalEventsKafkaTemplate.send(topicName, key, message)); + for (Map.Entry> entry : partitions.entrySet()) { + for (byte[] message : entry.getValue()) { + sendResults.add(externalEventsKafkaTemplate.send(topicName, entry.getKey(), message)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java index 33a560366a9..6278eba1c57 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java @@ -47,7 +47,6 @@ @RequiredArgsConstructor public class SchedulerJobListener implements JobListener { - private final String name = SchedulerServiceConstants.DEFAULT_LISTENER_NAME; private final SchedularWritePlatformService schedularService; private final AppUserRepositoryWrapper userRepository; private final BusinessDateReadPlatformService businessDateReadPlatformService; @@ -55,7 +54,7 @@ public class SchedulerJobListener implements JobListener { @Override public String getName() { - return this.name; + return SchedulerServiceConstants.DEFAULT_LISTENER_NAME; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java index 1061815f33a..31406327860 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType; import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; @@ -35,11 +34,10 @@ public interface ReportingProcessService { default Map getReportParams(final MultivaluedMap queryParams) { final Map reportParams = new HashMap<>(); - final Set keys = queryParams.keySet(); - for (final String k : keys) { - if (k.startsWith("R_")) { - String pKey = "${" + k.substring(2) + "}"; - String pValue = queryParams.get(k).get(0); + for (Map.Entry> entry : queryParams.entrySet()) { + if (entry.getKey().startsWith("R_")) { + String pKey = "${" + entry.getKey().substring(2) + "}"; + String pValue = entry.getValue().get(0); SQLInjectionValidator.validateSQLInput(pValue); reportParams.put(pKey, pValue); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java index 841a509ef01..e0242f269ee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java @@ -59,7 +59,7 @@ @RequiredArgsConstructor public class SmsApiResource { - private final String resourceNameForPermissions = "SMS"; + private static final String RESOURCE_NAME_FOR_PERMISSIONS = "SMS"; private final PlatformSecurityContext context; private final SmsReadPlatformService readPlatformService; @@ -69,7 +69,7 @@ public class SmsApiResource { @GET public String retrieveAll(@Context final UriInfo uriInfo) { - context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); + context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final Collection smsMessages = readPlatformService.retrieveAll(); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializer.serialize(settings, smsMessages); @@ -98,7 +98,7 @@ public String retrieveAllSmsByStatus(@PathParam("campaignId") final Long campaig @QueryParam("dateFormat") final String rawDateFormat, @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) { - context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); + context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final SearchParameters searchParameters = SearchParameters.forSMSCampaign(offset, limit, orderBy, sortOrder); final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResourceSwagger.java index 8f26777ff06..4cd6ba662d8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResourceSwagger.java @@ -86,7 +86,7 @@ private PostSurveySurveyNameApptableIdRequest() { @Schema(example = "201") public Long ppi_fryingpans_cd_q10_fryingpans; @Schema(example = "2014-12-02 20:30:00") - public ZonedDateTime Date; + public ZonedDateTime date; @Schema(example = "Y-m-d H:i:s") public ZonedDateTime dateFormat; @Schema(example = "en_GB") diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java index b881254669b..bd42683dd12 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java @@ -308,8 +308,10 @@ public String getTransactionsForCashier(@PathParam("tellerId") @Parameter(descri @QueryParam("limit") @Parameter(description = "limit") final Integer limit, @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy, @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { - final TellerData teller = this.readPlatformService.findTeller(tellerId); - final CashierData cashier = this.readPlatformService.findCashier(cashierId); + // TODO: can we remove these 2 calls? we don't use the results, but left it here in case something is done in + // the functions + this.readPlatformService.findTeller(tellerId); + this.readPlatformService.findCashier(cashierId); final LocalDate fromDate = null; final LocalDate toDate = null; @@ -334,8 +336,10 @@ public String getTransactionsWtihSummaryForCashier(@PathParam("tellerId") @Param @QueryParam("limit") @Parameter(description = "limit") final Integer limit, @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy, @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { - final TellerData teller = this.readPlatformService.findTeller(tellerId); - final CashierData cashier = this.readPlatformService.findCashier(cashierId); + // TODO: can we remove these 2 calls? we don't use the results, but left it here in case something is done in + // the functions + this.readPlatformService.findTeller(tellerId); + this.readPlatformService.findCashier(cashierId); final LocalDate fromDate = null; final LocalDate toDate = null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java index 3f649405e00..b00b5c98ba1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java @@ -355,8 +355,8 @@ private CommandProcessingResult doTransactionForCashier(final Long cashierId, fi this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json()); + // TODO: can we please remove this whole block?!? this is 20 lines of dead code!!! final String entityType = command.stringValueOfParameterNamed("entityType"); - final Long entityId = command.longValueOfParameterNamed("entityId"); if (entityType != null) { if (entityType.equals("loan account")) { // TODO : Check if loan account exists diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/service/AddressReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/service/AddressReadPlatformServiceImpl.java index ebe5660dd5f..5e0a60f4df9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/service/AddressReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/service/AddressReadPlatformServiceImpl.java @@ -65,8 +65,6 @@ public AddressData mapRow(final ResultSet rs, @SuppressWarnings("unused") final final long addressId = rs.getLong("id"); - final long clientId = rs.getLong("client_id"); - final String street = rs.getString("street"); final String address_line_1 = rs.getString("address_line_1"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index 6bfdf307975..9cb152acf72 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -294,7 +294,7 @@ public CommandProcessingResult createClient(final JsonCommand command) { validateParentGroupRulesBeforeClientActivation(newClient); runEntityDatatableCheck(newClient.getId(), newClient.getLegalForm()); final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateClient(null).build(); - rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); + rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser); } this.clientRepository.saveAndFlush(newClient); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java index ea464426ca7..dbe9c314750 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/ConditionType.java @@ -18,33 +18,37 @@ */ package org.apache.fineract.portfolio.common.domain; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Arrays; public enum ConditionType { INVALID(0, "ConditionType.invalid"), // LESSTHAN(1, "ConditionType.lessthan"), // EQUAL(2, "ConditionType.equal"), // + // TODO: fix typo "GREATERTHAN" GRETERTHAN(3, "ConditionType.greterthan"), // NOT_EQUAL(4, "ConditionType.notequal");// private final Integer value; private final String code; - private static final Map intToEnumMap = new HashMap<>(); - - static { - for (final ConditionType type : ConditionType.values()) { - intToEnumMap.put(type.value, type); + public static ConditionType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - } - public static ConditionType fromInt(final Integer ruleTypeValue) { - final ConditionType type = intToEnumMap.get(ruleTypeValue); - return type; + switch (v) { + case 1: + return LESSTHAN; + case 2: + return EQUAL; + case 3: + return GRETERTHAN; + case 4: + return NOT_EQUAL; + default: + return INVALID; + } } ConditionType(final Integer value, final String code) { @@ -65,34 +69,13 @@ public String getCode() { return this.code; } - public boolean isConditionTypeEqual() { - return ConditionType.EQUAL.getValue().equals(this.value); - } - - public boolean isConditionTypeGreterThan() { - return ConditionType.GRETERTHAN.getValue().equals(this.value); - } - - public boolean isConditionTypeNotEqual() { - return ConditionType.NOT_EQUAL.getValue().equals(this.value); - } - - public boolean isConditionTypeLessThan() { - return ConditionType.LESSTHAN.getValue().equals(this.value); - } - + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { return ConditionType.INVALID.getValue().equals(this.value); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final ConditionType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java index 22f9db63f11..646108a989d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java @@ -172,16 +172,16 @@ private CommandProcessingResult createGroupingType(final JsonCommand command, fi if (newGroup.isCenter()) { final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateCenter(null).build(); - rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); + rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser); } else { final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateGroup(null).build(); - rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); + rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser); } } if (!newGroup.isCenter() && newGroup.hasActiveClients()) { final CommandWrapper commandWrapper = new CommandWrapperBuilder().associateClientsToGroup(newGroup.getId()).build(); - rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); + rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser); } // pre-save to generate id for use in group hierarchy diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java index b5993c319d9..ff8208b6721 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveAttributeName.java @@ -18,10 +18,7 @@ */ package org.apache.fineract.portfolio.interestratechart.incentive; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Arrays; public enum InterestIncentiveAttributeName { @@ -34,19 +31,21 @@ public enum InterestIncentiveAttributeName { private final Integer value; private final String code; - private static final Map intToEnumMap = new HashMap<>(); - - static { - for (final InterestIncentiveAttributeName type : InterestIncentiveAttributeName.values()) { - intToEnumMap.put(type.value, type); + public static InterestIncentiveAttributeName fromInt(final Integer value) { + switch (value) { + case 2: + return GENDER; + case 3: + return AGE; + case 4: + return CLIENT_TYPE; + case 5: + return CLIENT_CLASSIFICATION; + default: + return INVALID; } } - public static InterestIncentiveAttributeName fromInt(final Integer ruleTypeValue) { - final InterestIncentiveAttributeName type = intToEnumMap.get(ruleTypeValue); - return type; - } - InterestIncentiveAttributeName(final Integer value, final String code) { this.value = value; this.code = code; @@ -65,49 +64,45 @@ public String getCode() { return this.code; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isGender() { - return InterestIncentiveAttributeName.GENDER.getValue().equals(this.value); + return GENDER.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isAge() { - return InterestIncentiveAttributeName.AGE.getValue().equals(this.value); + return AGE.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isClientType() { - return InterestIncentiveAttributeName.CLIENT_TYPE.getValue().equals(this.value); + return CLIENT_TYPE.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isClientClassification() { - return InterestIncentiveAttributeName.CLIENT_CLASSIFICATION.getValue().equals(this.value); + return CLIENT_CLASSIFICATION.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return InterestIncentiveAttributeName.INVALID.getValue().equals(this.value); + return INVALID.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public static boolean isCodeValueAttribute(InterestIncentiveAttributeName attributeName) { - boolean isCodeValue = false; switch (attributeName) { case GENDER: case CLIENT_TYPE: case CLIENT_CLASSIFICATION: - isCodeValue = true; - break; + return true; default: - break; + return false; } - return isCodeValue; } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final InterestIncentiveAttributeName enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java index b5008630e83..8f00b48cbd4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveEntityType.java @@ -18,10 +18,7 @@ */ package org.apache.fineract.portfolio.interestratechart.incentive; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Arrays; public enum InterestIncentiveEntityType { @@ -32,17 +29,19 @@ public enum InterestIncentiveEntityType { private final Integer value; private final String code; - private static final Map intToEnumMap = new HashMap<>(); - - static { - for (final InterestIncentiveEntityType type : InterestIncentiveEntityType.values()) { - intToEnumMap.put(type.value, type); + public static InterestIncentiveEntityType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - } - public static InterestIncentiveEntityType fromInt(final Integer ruleTypeValue) { - final InterestIncentiveEntityType type = intToEnumMap.get(ruleTypeValue); - return type; + switch (v) { + case 2: + return CUSTOMER; + case 3: + return ACCOUNT; + default: + return INVALID; + } } InterestIncentiveEntityType(final Integer value, final String code) { @@ -64,21 +63,15 @@ public String getCode() { } public boolean isCustomer() { - return InterestIncentiveEntityType.CUSTOMER.getValue().equals(this.value); + return CUSTOMER.equals(this); } public boolean isInvalid() { - return InterestIncentiveEntityType.INVALID.getValue().equals(this.value); + return INVALID.equals(this); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final InterestIncentiveEntityType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java index f14014d4da3..cc55c4e82c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/interestratechart/incentive/InterestIncentiveType.java @@ -18,10 +18,7 @@ */ package org.apache.fineract.portfolio.interestratechart.incentive; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Arrays; public enum InterestIncentiveType { @@ -32,17 +29,19 @@ public enum InterestIncentiveType { private final Integer value; private final String code; - private static final Map intToEnumMap = new HashMap<>(); - - static { - for (final InterestIncentiveType type : InterestIncentiveType.values()) { - intToEnumMap.put(type.value, type); + public static InterestIncentiveType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - } - public static InterestIncentiveType fromInt(final Integer ruleTypeValue) { - final InterestIncentiveType type = intToEnumMap.get(ruleTypeValue); - return type; + switch (v) { + case 2: + return FIXED; + case 3: + return INCENTIVE; + default: + return INVALID; + } } InterestIncentiveType(final Integer value, final String code) { @@ -63,27 +62,23 @@ public String getCode() { return this.code; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isIncentive() { - return InterestIncentiveType.INCENTIVE.getValue().equals(this.value); + return INCENTIVE.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isFixed() { - return InterestIncentiveType.FIXED.getValue().equals(this.value); + return FIXED.equals(this); } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return InterestIncentiveType.INVALID.getValue().equals(this.value); + return INVALID.equals(this); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final InterestIncentiveType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 1e84c80264d..c9eb31fb6aa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -84,7 +84,7 @@ public class LoanTransactionsApiResource { private final Set responseDataParameters = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", "externalId", LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME)); - private final String resourceNameForPermissions = "LOAN"; + private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN"; private final PlatformSecurityContext context; private final LoanReadPlatformService loanReadPlatformService; @@ -414,7 +414,7 @@ public String undoWaiveCharge( private String retrieveTransaction(final Long loanId, final String loanExternalIdStr, final Long transactionId, final String transactionExternalIdStr, final UriInfo uriInfo) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr); ExternalId transactionExternalId = ExternalIdFactory.produce(transactionExternalIdStr); @@ -488,7 +488,7 @@ private String executeTransaction(final Long loanId, final String loanExternalId private String retrieveTransactionTemplate(Long loanId, String loanExternalIdStr, String commandParam, UriInfo uriInfo, DateFormat dateFormat, DateParam transactionDateParam, String locale) { - this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index 20572c6a825..acff5c52b45 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -500,7 +500,7 @@ public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductD BigDecimal interestRatePerPeriod = null; Integer numberOfRepayments = null; - if (product.isUseBorrowerCycle() && loanCycleNumber > 0) { + if (product.isUseBorrowerCycle() && loanCycleNumber != null && loanCycleNumber > 0) { Collection principalVariationsForBorrowerCycle = product .getPrincipalVariationsForBorrowerCycle(); Collection interestForVariationsForBorrowerCycle = product diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java index bce7e33c415..bd09da8fcb3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java @@ -66,23 +66,23 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } List exceptions = new ArrayList<>(); - for (final Long loanId : overdueScheduleData.keySet()) { + for (Map.Entry> entry : overdueScheduleData.entrySet()) { try { - loanChargeWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId)); + loanChargeWritePlatformService.applyOverdueChargesForLoan(entry.getKey(), entry.getValue()); } catch (final PlatformApiDataValidationException e) { final List errors = e.getErrors(); for (final ApiParameterError error : errors) { - log.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId, + log.error("Apply Charges due for overdue loans failed for account {} with message: {}", entry.getKey(), error.getDeveloperMessage(), e); } exceptions.add(e); } catch (final AbstractPlatformDomainRuleException e) { - log.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId, + log.error("Apply Charges due for overdue loans failed for account {} with message: {}", entry.getKey(), e.getDefaultUserMessage(), e); exceptions.add(e); } catch (Exception e) { - log.error("Apply Charges due for overdue loans failed for account {}", loanId, e); + log.error("Apply Charges due for overdue loans failed for account {}", entry.getKey(), e); exceptions.add(e); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java index 3c3d2bd5c8a..690004ada68 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java @@ -96,6 +96,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon public void applyHolidayToRepaymentScheduleDates(Loan loan, Holiday holiday) { LocalDate adjustedRescheduleToDate = null; + boolean isResheduleToNextRepaymentDate = holiday.getReScheduleType().isResheduleToNextRepaymentDate(); if (holiday.getReScheduleType().isResheduleToNextRepaymentDate()) { adjustedRescheduleToDate = getNextRepaymentDate(loan, holiday); } else { @@ -103,7 +104,11 @@ public void applyHolidayToRepaymentScheduleDates(Loan loan, Holiday holiday) { } if (isRepaymentScheduleAdjustmentNeeded(adjustedRescheduleToDate)) { - adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate); + if (isResheduleToNextRepaymentDate) { + adjustAllRepaymentSchedules(loan, holiday, adjustedRescheduleToDate); + } else { + adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate); + } businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueHolidayBusinessEvent(loan)); } } @@ -144,6 +149,38 @@ private void adjustRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adju } } + private void adjustAllRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adjustedRescheduleToDate) { + final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); + ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, holiday.getFromDate()); + final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + + // first repayment's from date is same as disbursement date. + LocalDate tmpFromDate = loan.getDisbursementDate(); + + // Loop through all loanRepayments + List installments = loan.getRepaymentScheduleInstallments(); + for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { + final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); + + // update from date if it's not same as previous installment's due + // date. + if (!DateUtils.isEqual(tmpFromDate, loanRepaymentScheduleInstallment.getFromDate())) { + loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); + } + + if (!DateUtils.isBefore(oldDueDate, holiday.getFromDate())) { + // FIXME: AA do we need to apply non-working days. + // Assuming holiday's repayment reschedule to date cannot be + // created on a non-working day. + + adjustedRescheduleToDate = scheduledDateGenerator.generateNextRepaymentDate(adjustedRescheduleToDate, loanApplicationTerms, + false); + loanRepaymentScheduleInstallment.updateDueDate(adjustedRescheduleToDate); + } + tmpFromDate = loanRepaymentScheduleInstallment.getDueDate(); + } + } + private LocalDate getNextRepaymentDate(Loan loan, Holiday holiday) { LocalDate adjustedRescheduleToDate = null; final LocalDate rescheduleToDate = holiday.getToDate(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningConfig.java index 4fcf6f96a5a..564549fceae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningConfig.java @@ -39,7 +39,6 @@ public class GenerateLoanlossProvisioningConfig { private JobRepository jobRepository; @Autowired private PlatformTransactionManager transactionManager; - private StepBuilder steps; @Autowired private ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService; @Autowired diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java index 292be13c1c7..d1cd7ab392e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/setloandelinquencytags/SetLoanDelinquencyTagsTasklet.java @@ -75,7 +75,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon loanScheduleDelinquencyData = this.loanRepaymentScheduleInstallmentRepository .fetchLoanScheduleDataByDueDateAndObligationsMet(LoanStatus.ACTIVE.getValue(), businessDate, false, processedLoans); } - processedLoans = applyDelinquencyTagToLoans(loanScheduleDelinquencyData); + applyDelinquencyTagToLoans(loanScheduleDelinquencyData); return RepeatStatus.FINISHED; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java index a5738aec020..98d75ffc7a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java @@ -77,7 +77,8 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer : loanApplicationTerms.getSubmittedOnDate(); LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, - Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms)); + Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, + getPrincipalToBeScheduled(loanApplicationTerms, periodStartDate)); List periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams, chargesDueAtTimeOfDisbursement); @@ -233,12 +234,14 @@ private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set d.getActualDisbursementDate().equals(periodStartDate)).map(DisbursementData::getPrincipal) + .reduce(BigDecimal.ZERO, BigDecimal::add); + principalToBeScheduled = Money.of(loanApplicationTerms.getCurrency(), totalDisbursalAmountsOnThe); } else if (loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal(); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 0ec8d3ea142..36764969eab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -771,10 +771,9 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { graceApplicable = installment.getDueDate(); } } - Collection keySet = adjustDueDateVariations.keySet(); - dueDates.addAll(keySet); - for (final LocalDate date : keySet) { - LocalDate removeDate = adjustDueDateVariations.get(date); + dueDates.addAll(adjustDueDateVariations.keySet()); + for (Map.Entry entry : adjustDueDateVariations.entrySet()) { + LocalDate removeDate = entry.getValue(); if (removeDate != null) { dueDates.remove(removeDate); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index f15cb39bdeb..15f359f4bcd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -1453,8 +1453,6 @@ public CommandProcessingResult undoGLIMLoanApplicationApproval(final Long loanId @Override public CommandProcessingResult undoApplicationApproval(final Long loanId, final JsonCommand command) { - AppUser currentUser = getAppUserIfPresent(); - this.fromApiJsonDeserializer.validateForUndo(command.json()); final Loan loan = retrieveLoanBy(loanId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java index 52a76ac353b..795bbec22c2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanArrearsAgingServiceImpl.java @@ -125,14 +125,16 @@ public Map> getScheduleDate(String loanId) { @Override public void updateLoanArrearsAgeingDetails(final Loan loan) { - int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?", - Integer.class, loan.getId()); - String updateStatement = constructUpdateStatement(loan, count == 0); - if (updateStatement == null) { - String deletestatement = "DELETE FROM m_loan_arrears_aging WHERE loan_id=?"; - this.jdbcTemplate.update(deletestatement, loan.getId()); // NOSONAR - } else { - this.jdbcTemplate.update(updateStatement); + if (loan != null) { + int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?", + Integer.class, loan.getId()); + String updateStatement = constructUpdateStatement(loan, count == 0); + if (updateStatement == null) { + String deletestatement = "DELETE FROM m_loan_arrears_aging WHERE loan_id=?"; + this.jdbcTemplate.update(deletestatement, loan.getId()); // NOSONAR + } else { + this.jdbcTemplate.update(updateStatement); + } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index 4c4a1e6f0bc..3f4c2970799 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -1500,6 +1500,11 @@ private PutLoanProductsProductIdRequest() {} @Schema(example = "dd MMMM yyyy") public String dateFormat; + @Schema(example = "HORIZONTAL") + public String loanScheduleProcessingType; + @Schema(example = "CUMULATIVE") + public String loanScheduleType; + public PostLoanProductsRequest.AllowAttributeOverrides allowAttributeOverrides; public List rates; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.java index fae5105261e..f9f1caeb212 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/meeting/attendance/AttendanceType.java @@ -48,31 +48,24 @@ public String getCode() { return this.code; } - public static AttendanceType fromInt(final Integer attendanceTypeId) { - - if (attendanceTypeId == null) { - return AttendanceType.INVALID; + public static AttendanceType fromInt(final Integer v) { + if (v == null) { + return INVALID; } - AttendanceType attendanceType = AttendanceType.INVALID; - switch (attendanceTypeId) { + switch (v) { case 1: - attendanceType = AttendanceType.PRESENT; - break; + return PRESENT; case 2: - attendanceType = AttendanceType.ABSENT; - break; + return ABSENT; case 3: - attendanceType = AttendanceType.APPROVED; - break; + return APPROVED; case 4: - attendanceType = AttendanceType.LEAVE; - break; + return LEAVE; case 5: - attendanceType = AttendanceType.LATE; - break; + return LATE; + default: + return INVALID; } - return attendanceType; } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java index d2d498fdb84..52e3db711a5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/service/PaymentDetailWritePlatformServiceJpaRepositoryImpl.java @@ -51,7 +51,7 @@ public PaymentDetail createPaymentDetail(final JsonCommand command, final Map fromParsedJson(final JsonElement element) { if (element.isJsonObject()) { final JsonObject topLevelJsonElement = element.getAsJsonObject(); - final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); if (topLevelJsonElement.has(LoanProductConstants.RATES_PARAM_NAME) && topLevelJsonElement.get(LoanProductConstants.RATES_PARAM_NAME).isJsonArray()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java index 549f68caf27..5cde0b00981 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java @@ -65,6 +65,7 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.CommandParameterUtil; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.savings.DepositAccountType; @@ -190,7 +191,7 @@ public String retrieveOne(@PathParam("accountId") @Parameter(description = "acco this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { + if (!(CommandParameterUtil.is(chargeStatus, "all") || CommandParameterUtil.is(chargeStatus, "active") || CommandParameterUtil.is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); } @@ -313,39 +314,32 @@ public String handleCommands(@PathParam("accountId") @Parameter(description = "a final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.recurringDepositAccountActivation(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().recurringDepositAccountInterestCalculation(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT)) { - final CommandWrapper commandRequest = builder.updateDepositAmountForRecurringDepositAccount(accountId) + + CommandWrapper commandRequest = null; + if (CommandParameterUtil.is(commandParam, "reject")) { + commandRequest = builder.rejectRecurringDepositAccountApplication(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "withdrawnByApplicant")) { + commandRequest = builder.withdrawRecurringDepositAccountApplication(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "approve")) { + commandRequest = builder.approveRecurringDepositAccountApplication(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "undoapproval")) { + commandRequest = builder.undoRecurringDepositAccountApplication(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "activate")) { + commandRequest = builder.recurringDepositAccountActivation(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "undoactivate")) { + commandRequest = builder.recurringDepositAccountUndoActivation(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "calculateInterest")) { + commandRequest = builder.withNoJsonBody().recurringDepositAccountInterestCalculation(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT)) { + commandRequest = builder.updateDepositAmountForRecurringDepositAccount(accountId) .withJson(apiRequestBodyAsJson).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.recurringDepositAccountInterestPosting(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeRecurringDepositAccount(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "prematureClose")) { - final CommandWrapper commandRequest = builder.prematureCloseRecurringDepositAccount(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculatePrematureAmount")) { + } else if (CommandParameterUtil.is(commandParam, "postInterest")) { + commandRequest = builder.recurringDepositAccountInterestPosting(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "close")) { + commandRequest = builder.closeRecurringDepositAccount(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "prematureClose")) { + commandRequest = builder.prematureCloseRecurringDepositAccount(accountId).build(); + } else if (CommandParameterUtil.is(commandParam, "calculatePrematureAmount")) { final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query, @@ -355,19 +349,16 @@ public String handleCommands(@PathParam("accountId") @Parameter(description = "a DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); } - if (result == null) { + if (commandRequest == null) { throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "undoactivate", "calculateInterest", "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); } + CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); } - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - @DELETE @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @@ -393,7 +384,7 @@ public String accountClosureTemplate(@PathParam("accountId") @Parameter(descript this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); DepositAccountData account = null; - if (is(commandParam, "close")) { + if (CommandParameterUtil.is(commandParam, "close")) { account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.RECURRING_DEPOSIT, accountId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java index e584f6de4eb..520d443223a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResourceSwagger.java @@ -182,7 +182,7 @@ private GetSavingsSummary() {} public GetSavingsCurrency currency; @Schema(example = "0") - public Integer accountBalance; + public BigDecimal accountBalance; } @Schema(example = "1") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 349f1eb6009..4614d8c8fb8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -826,6 +826,10 @@ public Map activate(final AppUser currentUser, final JsonCommand return actualChanges; } + public Map undoActivate() { + return super.undoActivate(); + } + protected List sortTransactions(final List transactions) { final List listOfTransactionsSorted = new ArrayList<>(); listOfTransactionsSorted.addAll(transactions); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 49f52b7f837..a077c46dd10 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -2255,6 +2255,48 @@ public void undoTransaction(final Long transactionId) { } } } + + protected Map undoActivate() { + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.undoActivateAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName) + .failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final LocalDate businessDate = DateUtils.getBusinessLocalDate();; + + this.status = SavingsAccountStatusType.APPROVED.getValue(); + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + this.activatedOnDate = null; + this.activatedBy = null; + this.lockedInUntilDate = null; + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNOD_ACTIVATE, businessDate); + + // Undo Transactions + for (SavingsAccountTransaction transaction: getTransactions()) { + undoTransaction(transaction); + } + + return actualChanges; + } public void undoSavingsTransaction(final Long transactionId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.java index f0f2846661a..1191b278917 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsEvent.java @@ -28,6 +28,7 @@ public enum SavingsEvent { SAVINGS_APPLICATION_APPROVED("application.approval"), // SAVINGS_APPLICATION_APPROVAL_UNDO("application.approval.undo"), // SAVINGS_ACTIVATE("activate"), // + SAVINGS_UNOD_ACTIVATE("activate.undo"), // SAVINGS_UNDO_ACTIVATE("undo.activate"), // SAVINGS_DEPOSIT("deposit"), // SAVINGS_WITHDRAWAL("withdraw"), // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java index acd81ac6942..a73e4458551 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.portfolio.savings.handler; -import java.time.LocalDate; import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -43,9 +42,9 @@ public ApplyAnnualFeeSavingsAccountCommandHandler(final SavingsAccountWritePlatf @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - - @SuppressWarnings("unused") - final LocalDate annualFeeTransactionDate = command.localDateValueOfParameterNamed("annualFeeTransactionDate"); + // TODO: why do we keep this class when we literally do nothing here?!? + // final LocalDate annualFeeTransactionDate = + // command.localDateValueOfParameterNamed("annualFeeTransactionDate"); // return // this.writePlatformService.applyAnnualFee(command.getSavingsId(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivateRecurringDepositAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivateRecurringDepositAccountCommandHandler.java new file mode 100644 index 00000000000..bfe42cfee83 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UndoActivateRecurringDepositAccountCommandHandler.java @@ -0,0 +1,46 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.savings.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "RECURRINGDEPOSITACCOUNT", action = "UNDO_ACTIVATE") +public class UndoActivateRecurringDepositAccountCommandHandler implements NewCommandSourceHandler { + + private final DepositAccountWritePlatformService depositAccountWritePlatformService; + + @Autowired + public UndoActivateRecurringDepositAccountCommandHandler(final DepositAccountWritePlatformService depositAccountWritePlatformService) { + this.depositAccountWritePlatformService = depositAccountWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.depositAccountWritePlatformService.undoActivateRDAccount(command.entityId(), command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java index 09f4091735e..0eddedeab5c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformService.java @@ -35,6 +35,8 @@ public interface DepositAccountWritePlatformService { CommandProcessingResult activateRDAccount(Long savingsId, JsonCommand command); + CommandProcessingResult undoActivateRDAccount(Long savingsId, JsonCommand command); + CommandProcessingResult updateDepositAmountForRDAccount(Long savingsId, JsonCommand command); CommandProcessingResult depositToFDAccount(Long savingsId, JsonCommand command); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index ca211a921f1..058f006d6a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -254,6 +254,25 @@ public CommandProcessingResult undoActivateFDAccount(Long savingsId, JsonCommand .build(); } + @Transactional + @Override + public CommandProcessingResult undoActivateRDAccount(final Long savingsId, final JsonCommand command) { + final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId, + DepositAccountType.RECURRING_DEPOSIT); + checkClientOrGroupActive(account); + + final Map changes = account.undoActivate(); + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } + private Money getActivationCharge(final FixedDepositAccount account) { Money activationChargeAmount = Money.zero(account.getCurrency()); for (SavingsAccountCharge savingsAccountCharge : account.charges()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index e9d950f88c8..04dab5301bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -265,15 +265,8 @@ public void processPostActiveActions(final SavingsAccount account, final DateTim @Transactional @Override public CommandProcessingResult gsimDeposit(final Long gsimId, final JsonCommand command) { - Long parentSavingId = gsimId; - // GroupSavingsIndividualMonitoringparentSavings=gsimRepository.findById(parentSavingId).get(); - List childSavings = this.savingAccountRepositoryWrapper.findByGsimId(gsimId); - JsonArray savingsArray = command.arrayOfParameterNamed("savingsArray"); - JsonArray childAccounts = command.arrayOfParameterNamed("childAccounts"); - int count = 0; - CommandProcessingResult result = null; for (JsonElement element : savingsArray) { result = deposit(element.getAsJsonObject().get("childAccountId").getAsLong(), @@ -1489,13 +1482,6 @@ private void updateSavingsTransactionsDetails(SavingsAccount account, Set existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig()); } - @SuppressWarnings("unused") - private void updateSavingsTransactionsDetails(SavingsAccountData account, Set existingTransactionIds, - Set existingReversedTransactionIds) { - existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig()); - existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig()); - } - private void postJournalEntries(final SavingsAccount savingsAccount, final Set existingTransactionIds, final Set existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 57464a89489..e9c4fa0c2a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -656,7 +656,8 @@ private void checkClientOrGroupActive(final SavingsAccount account) { public CommandProcessingResult createActiveApplication(final SavingsAccountDataDTO savingsAccountDataDTO) { final CommandWrapper commandWrapper = new CommandWrapperBuilder().savingsAccountActivation(null).build(); - boolean rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, savingsAccountDataDTO.getAppliedBy()); + boolean rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, + savingsAccountDataDTO.getAppliedBy()); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsAccountDataDTO.getClient(), savingsAccountDataDTO.getGroup(), savingsAccountDataDTO.getSavingsProduct(), savingsAccountDataDTO.getApplicationDate(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java index 3a24de73505..295d3f55aee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java @@ -83,8 +83,8 @@ public void postInterest() throws JobExecutionException { } catch (DataAccessException exception) { log.error("Batch update failed due to DataAccessException", exception); errors.add(exception); - } catch (NullPointerException exception) { - log.error("Batch update failed due to NullPointerException", exception); + } catch (Exception exception) { + log.error("Batch update failed", exception); errors.add(exception); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java index 8b17c9605e6..f87037593b2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java @@ -114,7 +114,7 @@ public Page searchTransactions(@NotNull Long savi Object[] args = params.toArray(); String countQuery = "SELECT COUNT(*) " + tm.from() + where; - Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); + Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); // NOSONAR if (totalElements == null || totalElements == 0) { return emptyResult; } @@ -266,7 +266,7 @@ public Page queryAdvanced(@NotNull Long savingsId, @NotNull PagedLoc // Execute the count Query String countQuery = "SELECT COUNT(*)" + from + where; - Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); + Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); // NOSONAR if (totalElements == null || totalElements == 0) { return PageableExecutionUtils.getPage(results, pageable, () -> 0); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/SharePeriodFrequencyType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/SharePeriodFrequencyType.java index 2506ee6f7a2..ed237dc5e95 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/SharePeriodFrequencyType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/SharePeriodFrequencyType.java @@ -18,8 +18,7 @@ */ package org.apache.fineract.portfolio.shareproducts; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * An enumeration of supported calendar periods used in savings. @@ -48,39 +47,32 @@ public String getCode() { return this.code; } - public static SharePeriodFrequencyType fromInt(final Integer type) { - SharePeriodFrequencyType repaymentFrequencyType = SharePeriodFrequencyType.INVALID; - if (type != null) { - switch (type) { - case 0: - repaymentFrequencyType = SharePeriodFrequencyType.DAYS; - break; - case 1: - repaymentFrequencyType = SharePeriodFrequencyType.WEEKS; - break; - case 2: - repaymentFrequencyType = SharePeriodFrequencyType.MONTHS; - break; - case 3: - repaymentFrequencyType = SharePeriodFrequencyType.YEARS; - break; - } + public static SharePeriodFrequencyType fromInt(final Integer v) { + if (v == null) { + return INVALID; + } + + switch (v) { + case 0: + return DAYS; + case 1: + return WEEKS; + case 2: + return MONTHS; + case 3: + return YEARS; + default: + return INVALID; } - return repaymentFrequencyType; } + // TODO: why not just use the enum values... just more boilerplate code here!! public boolean isInvalid() { - return this.value.equals(SharePeriodFrequencyType.INVALID.value); + return this.equals(INVALID); } + // TODO: do we really need this?!? public static Object[] integerValues() { - final List values = new ArrayList<>(); - for (final SharePeriodFrequencyType enumType : values()) { - if (!enumType.isInvalid()) { - values.add(enumType.getValue()); - } - } - - return values.toArray(); + return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java index 9fe12fe1aa9..8fc798d81a7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/JpaTemplateDomainService.java @@ -81,7 +81,7 @@ public CommandProcessingResult updateTemplate(final Long templateId, final JsonC template.setText(command.stringValueOfParameterNamed(PROPERTY_TEXT)); template.setEntity(TemplateEntity.values()[command.integerValueSansLocaleOfParameterNamed(PROPERTY_ENTITY)]); final int templateTypeId = command.integerValueSansLocaleOfParameterNamed(PROPERTY_TYPE); - TemplateType type = null; + TemplateType type; switch (templateTypeId) { case 0: type = TemplateType.DOCUMENT; @@ -89,6 +89,8 @@ public CommandProcessingResult updateTemplate(final Long templateId, final JsonC case 2: type = TemplateType.SMS; break; + default: + type = null; } template.setType(type); diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java index 193ea27c6b4..c351fb9c533 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/PermissionWritePlatformServiceJpaRepositoryImpl.java @@ -55,19 +55,19 @@ public CommandProcessingResult updateMakerCheckerPermissions(final JsonCommand c final Map commandPermissions = permissionsCommand.getPermissions(); final Map changes = new HashMap<>(); final Map changedPermissions = new HashMap<>(); - for (final String permissionCode : commandPermissions.keySet()) { + for (Map.Entry entry : commandPermissions.entrySet()) { - final Permission permission = findPermissionInCollectionByCode(allPermissions, permissionCode); + final Permission permission = findPermissionInCollectionByCode(allPermissions, entry.getKey()); if (permission.getCode().endsWith("_CHECKER") || permission.getCode().startsWith("READ_") || permission.getGrouping().equalsIgnoreCase("special")) { - throw new PermissionNotFoundException(permissionCode); + throw new PermissionNotFoundException(entry.getKey()); } - final boolean isSelected = commandPermissions.get(permissionCode).booleanValue(); + final boolean isSelected = entry.getValue(); final boolean changed = permission.enableMakerChecker(isSelected); if (changed) { - changedPermissions.put(permissionCode, isSelected); + changedPermissions.put(entry.getKey(), isSelected); this.permissionRepository.saveAndFlush(permission); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java index c06ac8829a6..8c80ec75301 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/RoleWritePlatformServiceJpaRepositoryImpl.java @@ -148,13 +148,13 @@ public CommandProcessingResult updateRolePermissions(final Long roleId, final Js final Map commandPermissions = permissionsCommand.getPermissions(); final Map changes = new HashMap<>(); final Map changedPermissions = new HashMap<>(); - for (final String permissionCode : commandPermissions.keySet()) { - final boolean isSelected = commandPermissions.get(permissionCode).booleanValue(); + for (Map.Entry entry : commandPermissions.entrySet()) { + final boolean isSelected = entry.getValue(); - final Permission permission = findPermissionByCode(allPermissions, permissionCode); + final Permission permission = findPermissionByCode(allPermissions, entry.getKey()); final boolean changed = role.updatePermission(permission, isSelected); if (changed) { - changedPermissions.put(permissionCode, isSelected); + changedPermissions.put(entry.getKey(), isSelected); } } diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index fbbe8662e7d..81833a3c167 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -171,6 +171,8 @@ fineract.sampling.resetPeriodSec=${FINERACT_SAMPLING_RESET_PERIOD_IN_SEC:60} fineract.module.investor.enabled=${FINERACT_MODULE_INVESTOR_ENABLED:true} +fineract.insecure-http-client=${FINERACT_INSECURE_HTTP_CLIENT:true} + # Logging pattern for the console logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}} logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}] diff --git a/fineract-provider/src/main/resources/banner.txt b/fineract-provider/src/main/resources/banner.txt index 6103c57b914..31df8c9b46c 100644 --- a/fineract-provider/src/main/resources/banner.txt +++ b/fineract-provider/src/main/resources/banner.txt @@ -6,7 +6,7 @@ /_/ \_\ .__/ \__,_|\___|_| |_|\___| |_| |_|_| |_|\___|_| \__,_|\___|\__| |_| -${AnsiStyle.FAINT}${AnsiColor.WHITE}(c) 2015-2022 ${application.title} (https://fineract.apache.org)${AnsiColor.BLACK} +${AnsiStyle.FAINT}${AnsiColor.WHITE}(c) 2015-2024 ${application.title} (https://fineract.apache.org)${AnsiColor.BLACK} ${AnsiStyle.FAINT}${AnsiColor.WHITE}Powered by Spring Boot ${spring-boot.version}${AnsiColor.BLACK} diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index b71677198a7..b098fc5291c 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -150,4 +150,6 @@ + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0131_add_configuration_maker_checker.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0131_add_configuration_maker_checker.xml new file mode 100644 index 00000000000..a400511b8b5 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0131_add_configuration_maker_checker.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0132_deposits_undo_activation.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0132_deposits_undo_activation.xml new file mode 100644 index 00000000000..c08571d8bc0 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0132_deposits_undo_activation.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java index 8eb9a36dc12..9c8d14e8ba3 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java +++ b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java @@ -25,6 +25,7 @@ import java.util.List; import javax.sql.DataSource; import liquibase.change.custom.CustomTaskChange; +import okhttp3.OkHttpClient; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService; @@ -188,4 +189,10 @@ public JobRepository jobRepository() { public JobOperator jobOperator() { return mock(JobOperator.class, RETURNS_MOCKS); } + + @Primary + @Bean + public OkHttpClient okHttpClient() { + return mock(OkHttpClient.class, RETURNS_MOCKS); + } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/COBBusinessStepServiceStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/COBBusinessStepServiceStepDefinitions.java index c020be0cb60..7b3ca489996 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/COBBusinessStepServiceStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/COBBusinessStepServiceStepDefinitions.java @@ -26,10 +26,9 @@ import static org.mockito.Mockito.verify; import com.google.common.base.Splitter; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; -import java.math.BigDecimal; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.TreeMap; @@ -44,12 +43,12 @@ import org.apache.fineract.infrastructure.core.domain.ActionContext; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; -import org.apache.fineract.mix.data.MixTaxonomyData; import org.mockito.Mockito; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.context.ApplicationContext; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class COBBusinessStepServiceStepDefinitions implements En { private ApplicationContext applicationContext = mock(ApplicationContext.class); @@ -68,10 +67,6 @@ public class COBBusinessStepServiceStepDefinitions implements En { private AbstractAuditableCustom outputItem = mock(AbstractAuditableCustom.class); private AbstractAuditableCustom resultItem; - - private HashMap data = new HashMap<>(); - - private String result; private Class clazz; private String jobName; private BatchBusinessStep batchBusinessStep = mock(BatchBusinessStep.class); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/common/InitialisationTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/common/InitialisationTaskletStepDefinitions.java index 80f831f4129..17df55f2328 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/common/InitialisationTaskletStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/common/InitialisationTaskletStepDefinitions.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; import java.time.LocalDate; import java.time.ZoneId; @@ -41,6 +42,7 @@ import org.springframework.batch.repeat.RepeatStatus; import org.springframework.security.core.context.SecurityContextHolder; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class InitialisationTaskletStepDefinitions implements En { private static final LocalDate TODAY = LocalDate.now(ZoneId.systemDefault()); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java index e3f4ca94ad5..6899a21e529 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java @@ -66,7 +66,11 @@ public LoanItemListenerStepDefinitions() { }); When("LoanItemListener.onReadError method executed", () -> { - loanItemListener.onReadError(exception); + try { + loanItemListener.onReadError(exception); + } finally { + ThreadLocalContextUtil.reset(); + } }); Then("LoanItemListener.onReadError result should match", () -> { @@ -87,7 +91,11 @@ public LoanItemListenerStepDefinitions() { }); When("LoanItemListener.onProcessError method executed", () -> { - loanItemListener.onProcessError(loan, exception); + try { + loanItemListener.onProcessError(loan, exception); + } finally { + ThreadLocalContextUtil.reset(); + } }); Then("LoanItemListener.onProcessError result should match", () -> { @@ -108,7 +116,11 @@ public LoanItemListenerStepDefinitions() { }); When("LoanItemListener.onWriteError method executed", () -> { - loanItemListener.onWriteError(exception, new Chunk<>(List.of(loan))); + try { + loanItemListener.onWriteError(exception, new Chunk<>(List.of(loan))); + } finally { + ThreadLocalContextUtil.reset(); + } }); Then("LoanItemListener.onWriteError result should match", () -> { diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/AddPeriodicAccrualEntriesBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/AddPeriodicAccrualEntriesBusinessStepTest.java index 0e9cef6b2f6..84d0e956ac1 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/AddPeriodicAccrualEntriesBusinessStepTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/AddPeriodicAccrualEntriesBusinessStepTest.java @@ -42,6 +42,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService; import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -66,6 +67,11 @@ public void setUp() { underTest = new AddPeriodicAccrualEntriesBusinessStep(loanAccrualPlatformService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenLoanWithAccrual() throws MultiException { // given diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java index ead01296a3b..6e077b5e82e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; import java.time.LocalDate; import java.time.ZoneId; @@ -50,6 +51,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionTemplate; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class ApplyLoanLockTaskletStepDefinitions implements En { ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(List.class); @@ -120,7 +122,11 @@ public ApplyLoanLockTaskletStepDefinitions() { }); When("ApplyLoanLockTasklet.execute method executed", () -> { - resultItem = applyLoanLockTasklet.execute(stepContribution, null); + try { + resultItem = applyLoanLockTasklet.execute(stepContribution, null); + } finally { + ThreadLocalContextUtil.reset(); + } }); Then("ApplyLoanLockTasklet.execute result should match", () -> { diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java index 6b193ad7cd5..727cbdd51fa 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java @@ -46,6 +46,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -72,6 +73,11 @@ public void setUp() { underTest = new CheckLoanRepaymentDueBusinessStep(configurationDomainService, businessEventNotifierService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenLoanWithInstallmentDueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() { ArgumentCaptor loanRepaymentDueEvent = ArgumentCaptor.forClass(LoanRepaymentDueBusinessEvent.class); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java index 41f76b602e1..974d0d0ac23 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java @@ -43,6 +43,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -69,6 +70,11 @@ public void setUp() { underTest = new CheckLoanRepaymentOverdueBusinessStep(configurationDomainService, businessEventNotifierService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenLoanWithInstallmentOverdueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() { ArgumentCaptor loanRepaymentDueBusinessEventArgumentCaptor = ArgumentCaptor @@ -169,5 +175,4 @@ public void givenLoanWithInstallmentOverdueAfterConfiguredDaysInLoanProductWhenS assertEquals(repaymentInstallment, loanPayloadForEvent); assertEquals(processedLoan, loanForProcessing); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java index 15d0f31f3db..a5b19982a23 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; import java.time.LocalDate; import java.time.ZoneId; @@ -36,6 +37,7 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.item.ExecutionContext; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class LoanItemProcessorStepDefinitions implements En { private COBBusinessStepService cobBusinessStepService = mock(COBBusinessStepService.class); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java index 562d4b9a854..26d2be8b45c 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import com.google.common.base.Splitter; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; import java.time.LocalDate; import java.time.ZoneId; @@ -47,6 +48,7 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.item.ExecutionContext; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class LoanItemReaderStepDefinitions implements En { private LoanRepository loanRepository = mock(LoanRepository.class); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java index 2ebe0efbb77..cdd22a08240 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java @@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -73,6 +74,11 @@ class LoanItemReaderTest { @Mock private Loan loan; + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void testLoanItemReaderSimple() throws Exception { // given @@ -163,5 +169,4 @@ public void testLoanItemReaderMultiThreadRead() throws Exception { Mockito.verifyNoMoreInteractions(loanRepository); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStepTest.java index bdb22dd654f..6976991ce17 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStepTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/SetLoanDelinquencyTagsBusinessStepTest.java @@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -100,6 +101,11 @@ public void setUp() { delinquencyReadPlatformService, businessEventNotifierService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + /** * Tests {@link SetLoanDelinquencyTagsBusinessStep#execute(Loan)} success scenario. * diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/COBBulkEventConfigurationTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/COBBulkEventConfigurationTest.java index 31c7cb91d14..19a47b1f932 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/service/COBBulkEventConfigurationTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/service/COBBulkEventConfigurationTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.time.LocalDate; import java.time.ZoneId; import java.util.HashMap; @@ -54,6 +55,7 @@ import org.springframework.context.ApplicationContext; @ExtendWith(MockitoExtension.class) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class COBBulkEventConfigurationTest { @Mock @@ -83,7 +85,7 @@ public void setUp() throws Exception { @AfterEach public void tearDown() { - ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); + ThreadLocalContextUtil.reset(); } @Test diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java index 3324bb8d43f..bb3a855ff8e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.LocalDate; @@ -41,6 +42,7 @@ import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -52,6 +54,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") class InlineLoanCOBExecutorServiceImplTest { @InjectMocks @@ -71,6 +74,11 @@ class InlineLoanCOBExecutorServiceImplTest { @Mock private FineractProperties.FineractBodyItemSizeLimitProperties fineractBodyItemSizeLimitProperties; + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test void shouldExceptionThrownIfLoanIsAlreadyLocked() { JsonCommand command = mock(JsonCommand.class); diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTaskletTest.java b/fineract-provider/src/test/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTaskletTest.java index cacf22032ca..fa4ef94028f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTaskletTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/jobs/PurgeProcessedCommandsTaskletTest.java @@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -71,6 +72,11 @@ public void setUp() { underTest = new PurgeProcessedCommandsTasklet(repository, configurationDomainService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() { // given @@ -97,5 +103,4 @@ public void givenEventsForPurgeWhenExceptionOccursThenJobExecutionFinishesSucces // then assertEquals(RepeatStatus.FINISHED, resultStatus); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerExceptionStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerExceptionStepDefinitions.java index c5d84d1686b..17400444253 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerExceptionStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerExceptionStepDefinitions.java @@ -20,10 +20,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; import org.apache.fineract.commands.exception.UnsupportedCommandException; import org.springframework.beans.factory.annotation.Autowired; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class CommandHandlerExceptionStepDefinitions implements En { @Autowired diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java index 818e6d95f7e..c4d694e9da4 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java @@ -25,13 +25,10 @@ import io.cucumber.java8.En; import io.github.resilience4j.retry.RetryRegistry; import io.github.resilience4j.retry.event.RetryEvent; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandWrapper; -import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException; +import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; @@ -69,7 +66,7 @@ public CommandServiceStepDefinitions() { ReflectionTestUtils.setField(processAndLogCommandService, "fineractRequestContextHolder", contextHolder); Mockito.when(contextHolder.getAttribute(any(), any())).thenThrow(new CannotAcquireLockException("BLOW IT UP!!!")) .thenThrow(new ObjectOptimisticLockingFailureException("Dummy", new RuntimeException("BLOW IT UP!!!"))) - .thenThrow(new RollbackTransactionAsCommandIsNotApprovedByCheckerException(new DummyCommandSource())); + .thenThrow(new RollbackTransactionNotApprovedException(1L, null)); this.retryRegistry.retry("executeCommand").getEventPublisher().onRetry(event -> { log.warn("... retry event: {}", event); @@ -147,13 +144,4 @@ public Long deleteEntry(Long makerCheckerId) { return null; } } - - @Entity - @Table(name = "m_portfolio_command_source") - public static class DummyCommandSource extends CommandSource { - - public DummyCommandSource() { - setId(1L); - } - } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java index 6cf486bde38..c38ee5c3a95 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandSourceServiceTest.java @@ -21,6 +21,7 @@ import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING; import static org.mockito.ArgumentMatchers.any; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.time.ZoneId; import java.util.Optional; import org.apache.fineract.batch.exception.ErrorInfo; @@ -44,6 +45,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class CommandSourceServiceTest { @Mock @@ -87,24 +89,12 @@ public void testCreateFromWrapper() { @Test public void testCreateFromExisting() { - CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L); long commandId = 1L; - JsonCommand jsonCommand = JsonCommand.fromExistingCommand(commandId, "", null, null, null, 1L, null, null, null, null, null, null, - null, null, null, null, null); CommandSource commandMock = Mockito.mock(CommandSource.class); - Mockito.when(commandSourceRepository.saveAndFlush(commandMock)).thenReturn(commandMock); Mockito.when(commandSourceRepository.findById(commandId)).thenReturn(Optional.of(commandMock)); - AppUser appUser = Mockito.mock(AppUser.class); - - ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "t1", "n1", ZoneId.systemDefault().toString(), null)); - - CommandSource actual = underTest.saveInitialNewTransaction(wrapper, jsonCommand, appUser, "idk"); - ArgumentCaptor commandSourceArgumentCaptor = ArgumentCaptor.forClass(CommandSource.class); - Mockito.verify(commandSourceRepository).saveAndFlush(commandSourceArgumentCaptor.capture()); - - CommandSource captured = commandSourceArgumentCaptor.getValue(); - Assertions.assertEquals(actual, captured); + CommandSource actual = underTest.getCommandSource(commandId); + Assertions.assertEquals(commandMock, actual); } @Test diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/service/SynchronousCommandProcessingServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/commands/service/SynchronousCommandProcessingServiceTest.java index 1c9e0249d64..4e6e0c43649 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/service/SynchronousCommandProcessingServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/service/SynchronousCommandProcessingServiceTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jakarta.servlet.http.HttpServletRequest; import java.util.Map; import org.apache.fineract.commands.domain.CommandProcessingResultType; @@ -48,6 +49,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class SynchronousCommandProcessingServiceTest { @Mock @@ -65,8 +67,6 @@ public class SynchronousCommandProcessingServiceTest { @Mock private IdempotencyKeyResolver idempotencyKeyResolver; @Mock - private IdempotencyKeyGenerator idempotencyKeyGenerator; - @Mock private CommandSourceService commandSourceService; @Spy @@ -91,19 +91,21 @@ public void testExecuteCommandSuccess() { when(commandWrapper.isNoteResource()).thenReturn(false); when(commandWrapper.isSurveyResource()).thenReturn(false); when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false); + + long commandId = 1L; JsonCommand jsonCommand = Mockito.mock(JsonCommand.class); + when(jsonCommand.commandId()).thenReturn(commandId); - NewCommandSourceHandler newCommandSourceHandler = Mockito.mock(NewCommandSourceHandler.class); + NewCommandSourceHandler commandHandler = Mockito.mock(NewCommandSourceHandler.class); CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class); when(commandProcessingResult.isRollbackTransaction()).thenReturn(false); - when(newCommandSourceHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult); - when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler); + when(commandHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult); + when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler); when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false); String idk = "idk"; when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk); CommandSource commandSource = Mockito.mock(CommandSource.class); - long commandId = 1L; when(commandSource.getId()).thenReturn(commandId); when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null); when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource); @@ -114,9 +116,12 @@ public void testExecuteCommandSuccess() { when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue()); when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser); + when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false)) + .thenReturn(commandProcessingResult); + CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false); - verify(commandSourceService).saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk); + verify(commandSourceService).getCommandSource(commandId); assertEquals(CommandProcessingResultType.PROCESSED.getValue(), commandSource.getStatus()); verify(commandSourceService).saveResultSameTransaction(commandSource); @@ -131,19 +136,21 @@ public void testExecuteCommandFails() { when(commandWrapper.isSurveyResource()).thenReturn(false); when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false); JsonCommand jsonCommand = Mockito.mock(JsonCommand.class); + Long commandId = jsonCommand.commandId(); - NewCommandSourceHandler newCommandSourceHandler = Mockito.mock(NewCommandSourceHandler.class); + NewCommandSourceHandler commandHandler = Mockito.mock(NewCommandSourceHandler.class); CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class); CommandSource commandSource = Mockito.mock(CommandSource.class); when(commandProcessingResult.isRollbackTransaction()).thenReturn(false); RuntimeException runtimeException = new RuntimeException("foo"); - when(newCommandSourceHandler.processCommand(jsonCommand)).thenThrow(runtimeException); - when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler); + when(commandHandler.processCommand(jsonCommand)).thenThrow(runtimeException); + when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler); when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false); String idk = "idk"; when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk); when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null); + when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource); AppUser appUser = Mockito.mock(AppUser.class); when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser); @@ -153,11 +160,14 @@ public void testExecuteCommandFails() { when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(initialCommandSource); + when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false)) + .thenThrow(runtimeException); + Assertions.assertThrows(RuntimeException.class, () -> { underTest.executeCommand(commandWrapper, jsonCommand, false); }); - verify(commandSourceService).saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk); + verify(commandSourceService).getCommandSource(commandId); verify(commandSourceService).generateErrorInfo(runtimeException); } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java index a73f7d613d0..eaabf63932e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.time.LocalDate; import java.time.ZoneId; import java.util.Optional; @@ -41,6 +42,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -55,6 +57,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class BusinessDateWritePlatformServiceTest { @InjectMocks @@ -77,6 +80,11 @@ public void init() { ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void businessDateIsNotEnabled() { JsonCommand command = JsonCommand.from(""); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java index 0e72639e074..bec504a26d5 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java @@ -105,7 +105,7 @@ public LiquibaseStepDefinitions() { }); Then("The database migration did not do anything", () -> { - verify(databaseStateVerifier).isLiquibaseDisabled(); + assertThat(verify(databaseStateVerifier).isLiquibaseDisabled()).isFalse(); verifyNoMoreInteractions(databaseStateVerifier); verifyNoInteractions(tenantDetailsService, tenantStoreDataSource, liquibaseFactory, tenantDataSourceFactory); }); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/MultiExceptionStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/MultiExceptionStepDefinitions.java index 86539a49b24..b4fce117f3f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/MultiExceptionStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/MultiExceptionStepDefinitions.java @@ -20,18 +20,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.java8.En; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.exception.MultiException; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class MultiExceptionStepDefinitions implements En { private List exceptions = new ArrayList<>(); - private MultiException multiException; - public MultiExceptionStepDefinitions() { Given("/^A multi exception with exceptions (.*) and (.*)$/", (String exception1, String exception2) -> { if (!StringUtils.isBlank(exception1)) { diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java index 6bdcb6433ae..52844609d54 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java @@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -48,6 +49,11 @@ public void init() { .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.of("Asia/Kolkata"))))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void markCreated() { MappingContext mappingContext = Mockito.mock(MappingContext.class); @@ -111,5 +117,4 @@ public void markCreatedOldDateTimeProvider() { assertEquals(now.getHour(), targetObject.getCreatedDate().get().getHour()); assertEquals(now.getMinute(), targetObject.getCreatedDate().get().getMinute()); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java index ae59614f189..1dec4df7a2c 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java @@ -28,6 +28,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,6 +39,11 @@ public void init() { ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void instanceDateProvider() { Optional dateTimeProvider = CustomDateTimeProvider.INSTANCE.getNow(); @@ -65,5 +71,4 @@ public void tenantDateProvider() { assertEquals(now.getHour(), ((OffsetDateTime) dateTimeProvider.get()).getHour()); assertEquals(now.getMinute(), ((OffsetDateTime) dateTimeProvider.get()).getMinute()); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/DateUtilsTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/DateUtilsTest.java index c5b78065bb0..ee7ef37f2f9 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/DateUtilsTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/DateUtilsTest.java @@ -32,6 +32,7 @@ import java.util.TimeZone; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,6 +44,11 @@ public void init() { ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 12)))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void getSystemZoneIdTest() { TimeZone.setDefault(TimeZone.getTimeZone("GMT+02:00")); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java index 47fb2e845d6..26e545af628 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java @@ -32,8 +32,10 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.JsonParser; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.ZoneId; @@ -71,25 +73,26 @@ import org.mockito.MockitoAnnotations; import org.mockito.Spy; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest { @Spy private FromJsonHelper fromJsonHelper = new FromJsonHelper(); - @Spy - private CreditBureauTokenCommandFromApiJsonDeserializer fromApiJsonDeserializer = new CreditBureauTokenCommandFromApiJsonDeserializer( - fromJsonHelper); @Mock private OkHttpClient okHttpClient; @Mock private CreditBureauConfigurationRepositoryWrapper configurationRepositoryWrapper; + @Mock + private TokenRepositoryWrapper tokenRepositoryWrapper; + @Mock private PlatformSecurityContext platformSecurityContext; @Mock - private TokenRepositoryWrapper tokenRepositoryWrapper; + private CreditBureauTokenCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final ObjectMapper mapper = new ObjectMapper(); @@ -261,7 +264,7 @@ public void okhttpUploadCreditReportTest() throws IOException { String jsonResponse = createResponseObjectArrayData(() -> "UPLOADED", data -> data); Path temp = Files.createTempFile("upload_test" + System.currentTimeMillis(), ".data"); - Files.writeString(temp, "test"); + Files.writeString(temp, "test", StandardCharsets.UTF_8); mockOkHttpCall(request -> { assertEquals(request.header("Authorization"), "Bearer AccessToken"); @@ -474,7 +477,7 @@ public void addCreditReportTest() throws IOException { String jsonResponse = createResponseObjectArrayData(() -> "ADD_CREDIT_RESPONSE", data -> data); Path temp = Files.createTempFile("add_credit_report" + System.currentTimeMillis(), ".data"); - Files.writeString(temp, "test"); + Files.writeString(temp, "test", StandardCharsets.UTF_8); mockOkHttpCall(request -> { if (request.url().host().equals("addcredit.report.url")) { diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableExportUtilTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableExportUtilTest.java index 1ff9c2944f0..d6ea10e359f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableExportUtilTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableExportUtilTest.java @@ -21,12 +21,26 @@ import java.util.Collections; import java.util.Map; import java.util.TreeMap; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.dataqueries.service.export.DatatableExportUtil; import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class DatatableExportUtilTest { + @BeforeEach + public void setUp() { + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + } + + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void emptyFolderTest() { Assert.assertEquals("", DatatableExportUtil.normalizeFolderName("")); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java index f38920ce862..b8bd9476c0f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import com.google.gson.JsonObject; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.List; import java.util.stream.Stream; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; @@ -49,6 +50,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class ReadWriteNonCoreDataServiceImplTest { @Mock diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/PurgeExternalEventsTaskletTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/PurgeExternalEventsTaskletTest.java index a956ba405c6..2bfbaf3828f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/PurgeExternalEventsTaskletTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/PurgeExternalEventsTaskletTest.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -69,6 +70,11 @@ public void setUp() { underTest = new PurgeExternalEventsTasklet(repository, configurationDomainService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() { // given @@ -95,5 +101,4 @@ public void givenEventsForPurgeWhenExceptionOccursThenJobExecutionFinishesSucces // then assertEquals(RepeatStatus.FINISHED, resultStatus); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTaskletTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTaskletTest.java index a2fe79b9706..df7e3215b8b 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTaskletTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTaskletTest.java @@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventView; import org.apache.fineract.infrastructure.event.external.service.message.MessageFactory; import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -97,6 +98,11 @@ public void setUp() { configurationDomainService); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + private void configureExternalEventsProducerReadBatchSizeProperty() { FineractProperties.FineractEventsProperties eventsProperties = new FineractProperties.FineractEventsProperties(); FineractProperties.FineractExternalEventsProperties externalProperties = new FineractProperties.FineractExternalEventsProperties(); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducerTest.java index 21ab34de8e9..a76ba420d58 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/producer/kafka/KafkaExternalEventProducerTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.times; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.nio.charset.Charset; import java.util.List; import java.util.Map; @@ -36,6 +37,7 @@ import org.springframework.kafka.support.SendResult; @ExtendWith(MockitoExtension.class) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") class KafkaExternalEventProducerTest { public static final String TOPIC_NAME = "unit-test"; diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEventTest.java index 02f12a8d950..39bb3b780d2 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/repository/domain/ExternalEventTest.java @@ -26,10 +26,16 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; class ExternalEventTest { + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void testConstructorWorks() { // given @@ -56,6 +62,5 @@ public void testConstructorWorks() { assertThat(result.getBusinessDate()).isEqualTo(currentBusinessDate); assertThat(result.getCreatedAt()).isNotNull(); assertThat(result.getSentAt()).isNull(); - } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/CustomExternalEventConfigurationRepositoryImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/CustomExternalEventConfigurationRepositoryImplTest.java index 1793ac928a5..ed708f14429 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/CustomExternalEventConfigurationRepositoryImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/CustomExternalEventConfigurationRepositoryImplTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jakarta.persistence.EntityManager; import org.apache.fineract.infrastructure.event.external.exception.ExternalEventConfigurationNotFoundException; import org.apache.fineract.infrastructure.event.external.repository.CustomExternalEventConfigurationRepositoryImpl; @@ -35,6 +36,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class CustomExternalEventConfigurationRepositoryImplTest { @Mock diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java index b89b0ca2a9f..8aaea6ff88f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java @@ -57,6 +57,7 @@ import org.apache.fineract.investor.enricher.LoanAccountDataV1Enricher; import org.apache.fineract.investor.enricher.LoanTransactionAdjustmentDataV1Enricher; import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -110,6 +111,11 @@ public void setUp() { .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void testPostEventShouldFailWhenNullEventIsGiven() { // given diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java index 0002caec360..22a9aa3784e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java @@ -70,6 +70,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -105,6 +106,11 @@ public void setUp() { .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void testLoanDelinquencyRangeEventPayloadSerialization() throws IOException { // given @@ -340,5 +346,4 @@ private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge charge) ReflectionTestUtils.setField(loanCharge, "id", 1L); return loanCharge; } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializerTest.java index f3b724c83a9..9bb321ec934 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAdjustTransactionBusinessEventSerializerTest.java @@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -78,6 +79,11 @@ public void setUp() { .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test void loanTransactionReversedOnDateSerializationTest() { Loan loanForProcessing = Mockito.mock(Loan.class); @@ -107,5 +113,4 @@ void loanTransactionReversedOnDateSerializationTest() { .toAvroDTO(businessEvent); assertEquals(reversedLocalDate, loanTransactionAdjustmentDataV1.getTransactionToAdjust().getReversedOnDate()); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java index 6519ea60473..5e50faa2e7f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java @@ -53,7 +53,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -91,6 +90,7 @@ public void setUp() { @AfterEach public void reset() { + ThreadLocalContextUtil.reset(); moneyHelper.close(); } @@ -153,7 +153,6 @@ public void testLoanRepaymentEventLoanIdMandatoryFieldValidation() { LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().plusDays(1); Loan loanForProcessing = Mockito.mock(Loan.class); - LoanProduct loanProduct = Mockito.mock(LoanProduct.class); LoanSummary loanSummary = Mockito.mock(LoanSummary.class); MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); @@ -183,5 +182,4 @@ public void testLoanRepaymentEventLoanIdMandatoryFieldValidation() { AvroRuntimeException exceptionThrown = assertThrows(AvroRuntimeException.class, () -> serializer.toAvroDTO(event)); assertTrue(exceptionThrown.getMessage().contains("does not accept null values")); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java index f99f300c04b..361310230ca 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java @@ -53,10 +53,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.http.HttpStatus; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -100,6 +100,11 @@ public void setUp() { testObj = new LoanCOBApiFilter(helper); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test void shouldLoanAndExternalMatchToo() { String externalId = UUID.randomUUID().toString(); @@ -254,7 +259,6 @@ void shouldProceedWhenRescheduleLoanIsNotLockedAndNotBehind() throws ServletExce given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); - LoanRescheduleRequest rescheduleRequest = mock(LoanRescheduleRequest.class); given(loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(resourceId)).willReturn(Optional.of(2L)); given(context.authenticatedUser()).willReturn(appUser); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java index a76e2ee072e..cda6583edbb 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java @@ -18,10 +18,12 @@ */ package org.apache.fineract.infrastructure.security.utils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Arrays; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class SQLInjectionValidatorTest { private static final String[] DDL_COMMANDS = { "create", "drop", "alter", "truncate", "comment", "sleep" }; diff --git a/fineract-provider/src/test/java/org/apache/fineract/investor/event/EnricherTest.java b/fineract-provider/src/test/java/org/apache/fineract/investor/event/EnricherTest.java index 64a49561b57..8d931f4517e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/investor/event/EnricherTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/investor/event/EnricherTest.java @@ -45,6 +45,7 @@ import org.apache.fineract.investor.enricher.LoanAccountDataV1Enricher; import org.apache.fineract.investor.enricher.LoanChargeDataV1Enricher; import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -53,19 +54,24 @@ @ExtendWith(MockitoExtension.class) public class EnricherTest { - private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault()); + private static final LocalDate ACTUAL_DATE = LocalDate.now(ZoneId.systemDefault()); - private final String purchasePriceRatio = "100.123"; - private final String settlementDate = DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now(ZoneId.systemDefault())); + private static final String PURCHASE_PRICE_RATIO = "100.123"; + private static final String SETTLEMENT_DATE = DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now(ZoneId.systemDefault())); @BeforeEach public void setUp() { ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); - ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate))); + ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, ACTUAL_DATE))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void testLoanAccountDataEnricher() { LoanAccountDataV1Enricher loanAccountDataV1Enricher = mock(LoanAccountDataV1Enricher.class); @@ -73,8 +79,8 @@ public void testLoanAccountDataEnricher() { doAnswer(a -> { LoanAccountDataV1 data = a.getArgument(0, LoanAccountDataV1.class); data.setExternalOwnerId("1"); - data.setSettlementDate(settlementDate); - data.setPurchasePriceRatio(purchasePriceRatio); + data.setSettlementDate(SETTLEMENT_DATE); + data.setPurchasePriceRatio(PURCHASE_PRICE_RATIO); return null; }).when(loanAccountDataV1Enricher).enrich(any(LoanAccountDataV1.class)); @@ -86,8 +92,8 @@ public void testLoanAccountDataEnricher() { verify(loanAccountDataV1Enricher, times(1)).enrich(any(LoanAccountDataV1.class)); assertEquals("1", original.getExternalOwnerId()); - assertEquals(settlementDate, original.getSettlementDate()); - assertEquals(purchasePriceRatio, original.getPurchasePriceRatio()); + assertEquals(SETTLEMENT_DATE, original.getSettlementDate()); + assertEquals(PURCHASE_PRICE_RATIO, original.getPurchasePriceRatio()); } @Test @@ -143,8 +149,8 @@ public void testAllEventEnricherWorksCorrectly() { doAnswer(a -> { LoanAccountDataV1 data = a.getArgument(0, LoanAccountDataV1.class); data.setExternalOwnerId("1"); - data.setSettlementDate(settlementDate); - data.setPurchasePriceRatio(purchasePriceRatio); + data.setSettlementDate(SETTLEMENT_DATE); + data.setPurchasePriceRatio(PURCHASE_PRICE_RATIO); return null; }).when(loanAccountDataV1Enricher).enrich(any(LoanAccountDataV1.class)); @@ -174,9 +180,8 @@ public void testAllEventEnricherWorksCorrectly() { verify(loanTransactionDataV1Enricher, times(1)).isDataTypeSupported(any()); assertEquals("1", data.getExternalOwnerId()); - assertEquals(settlementDate, data.getSettlementDate()); - assertEquals(purchasePriceRatio, data.getPurchasePriceRatio()); + assertEquals(SETTLEMENT_DATE, data.getSettlementDate()); + assertEquals(PURCHASE_PRICE_RATIO, data.getPurchasePriceRatio()); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java index 67ad3e7f77e..7bd0c22c562 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java @@ -76,6 +76,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -130,6 +131,11 @@ public void setUp() { .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaised() { ArgumentCaptor loanDeliquencyRangeChangeEvent = ArgumentCaptor @@ -614,5 +620,4 @@ public void givenLoanAccountWhenBackdatedPauseActionThenLoanDelinquencyRangeChan Loan loanPayloadForEvent = loanDelinquencyRangeChangeEvent.getValue().get(); assertEquals(loanForProcessing, loanPayloadForEvent); } - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java index 1e2b5d27be3..67cd74e7140 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java @@ -105,6 +105,7 @@ public void setUp() { @AfterEach public void deregister() { + ThreadLocalContextUtil.reset(); moneyHelperStatic.close(); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java index d5c8401a59a..a62e1f2dcca 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java @@ -27,16 +27,23 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.OffsetDateTime; +import java.time.ZoneId; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; @@ -46,11 +53,21 @@ */ public class LoanTest { + private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault()); + + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + /** * Tests {@link Loan#getCharges()} with charges. */ @Test public void testGetChargesWithCharges() { + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate))); + Loan loan = new Loan(); ReflectionTestUtils.setField(loan, "charges", Collections.singleton(buildLoanCharge())); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java index 7cffe275e5a..be3f43f5bc1 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java @@ -48,6 +48,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -83,6 +84,11 @@ public void setUp() { ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + @Test public void chargePaymentTransactionTestWithExactAmount() { final BigDecimal chargeAmount = BigDecimal.valueOf(100); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java index 6f3b8993298..19213fdf149 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java @@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -104,6 +105,11 @@ public void setUp() { ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + // IN ADVANCE @Test public void inAdvancePaymentOfPrincipal() { diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java index f817bab0037..b566bb29d7f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java @@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -105,6 +106,11 @@ public void setUp() { ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate))); } + @AfterEach + public void tearDown() { + ThreadLocalContextUtil.reset(); + } + // IN ADVANCE @Test public void inAdvancePaymentOfPrincipal() { diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java index eae0bfa6928..def517ca903 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java @@ -69,6 +69,7 @@ public void setUp() { @AfterEach public void reset() { + ThreadLocalContextUtil.reset(); moneyHelper.close(); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java index 824f22c15ca..9517041801b 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Optional; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; @@ -37,6 +38,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") class ReplayedTransactionBusinessEventServiceIntegrationTest { @Mock diff --git a/fineract-war/setenv.sh b/fineract-war/setenv.sh index 5d69b5aba81..2d48674e5c2 100644 --- a/fineract-war/setenv.sh +++ b/fineract-war/setenv.sh @@ -53,3 +53,4 @@ export FINERACT_DEFAULT_TENANTDB_TIMEZONE="Asia/Kolkata" export FINERACT_DEFAULT_TENANTDB_IDENTIFIER="default" export FINERACT_DEFAULT_TENANTDB_NAME="fineract_default" export FINERACT_DEFAULT_TENANTDB_DESCRIPTION="Default Demo Tenant" +export FINERACT_INSECURE_HTTP_CLIENT="true" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f09..7f93135c49b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586a54a..eed8d3ce134 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=100000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c20..1aa94a42690 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 53a6b238d41..6689b85beec 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/integration-tests/dependencies.gradle b/integration-tests/dependencies.gradle index 206bd6a7abc..460d5c33f09 100644 --- a/integration-tests/dependencies.gradle +++ b/integration-tests/dependencies.gradle @@ -20,7 +20,7 @@ dependencies { // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // - tomcat 'org.apache.tomcat:tomcat:10.1.16@zip' + tomcat 'org.apache.tomcat:tomcat:10.1.17@zip' testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"), project(path: ':fineract-core', configuration: 'runtimeElements'), project(path: ':fineract-investor', configuration: 'runtimeElements'), diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index dcf631239f1..380da49213d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -3033,6 +3033,63 @@ public void uc119() { }); } + // UC120: Advanced payment allocation with auto down payment and multiple disbursement on the first day + // ADVANCED_PAYMENT_ALLOCATION_STRATEGY + // 1. Create a Loan product with Adv. Pment. Alloc., and auto down payment + // 2. Submit Loan and approve + // 3. Disburse only 100 from 1000 + // 4. Disburse again on the same day but now 901 + @Test + public void uc120() { + runAt("22 November 2023", () -> { + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", true, LoanScheduleType.PROGRESSIVE, + LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "22 November 2023", "01 January 2023"); + + loanTransactionHelper.approveLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("22 November 2023").locale("en")); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("22 November 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(100.0)).locale("en")); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 75.0, 25.0, 75.0, 25.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 22), 25.0, 25.0, 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 7), 25.0, 0.0, 25.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 22), 25.0, 0.0, 25.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 1, 6), 25.0, 0.0, 25.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("22 November 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(901.0)).locale("en")); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 750.75, 250.25, 750.75, 250.25, null); + validatePeriod(loanDetails, 0, LocalDate.of(2023, 11, 22), null, 100.0, null, null, null, 0.0, 0.0, null, null, null, null, + null, null, null, null, null); + validatePeriod(loanDetails, 1, LocalDate.of(2023, 11, 22), null, 901.0, null, null, null, 0.0, 0.0, null, null, null, null, + null, null, null, null, null); + validatePeriod(loanDetails, 2, LocalDate.of(2023, 11, 22), LocalDate.of(2023, 11, 22), 976.0, 25.0, 25.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 3, LocalDate.of(2023, 11, 22), LocalDate.of(2023, 11, 22), 750.75, 225.25, 225.25, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 4, LocalDate.of(2023, 12, 7), null, 500.50, 250.25, 0.0, 250.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 5, LocalDate.of(2023, 12, 22), null, 250.25, 250.25, 0.0, 250.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 6, LocalDate.of(2024, 1, 6), null, 0.0, 250.25, 0.0, 250.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + }); + } + private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment, Double principalOutstanding, Double principalPaid, Double totalOverpaid) { assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java index 5808e3a3a7e..ad456b8f731 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java @@ -2154,12 +2154,7 @@ public void shouldNotFindAnyDatatableEntryByQueryAPIAndFailsToUpdateItsColumn() LOG.info("Batch Response : {}", new Gson().toJson(responseOfQueryAndUpdateDatatableBatch)); - final BatchResponse queryResponse = responseOfQueryAndUpdateDatatableBatch.get(0); - - Assertions.assertEquals(1L, queryResponse.getRequestId()); - Assertions.assertEquals(HttpStatus.SC_OK, queryResponse.getStatusCode(), "Verify Status Code 200 for query datatable entry"); - - final BatchResponse updateResponse = responseOfQueryAndUpdateDatatableBatch.get(1); + final BatchResponse updateResponse = responseOfQueryAndUpdateDatatableBatch.get(0); Assertions.assertEquals(2L, updateResponse.getRequestId()); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, updateResponse.getStatusCode(), diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index b0287e94a6f..3618bd14df4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -28,6 +28,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; @@ -127,6 +128,7 @@ */ @SuppressWarnings({ "rawtypes", "unchecked" }) @ExtendWith(LoanTestLifecycleExtension.class) +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class ClientLoanIntegrationTest { static { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java index f7c8284fb08..90207ebb8e3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java @@ -49,6 +49,7 @@ import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; +import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -266,7 +267,7 @@ public void testVerifyLoanDelinquencyRecalculationForBackdatedPauseDelinquencyAc Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); // Create Loan Product - Long loanProductId = createLoanProductWith25PctDownPaymentAndDelinquencyBucket(true, true, true); + Long loanProductId = createLoanProductWith25PctDownPaymentAndDelinquencyBucket(true, true, true, 3); // Apply and Approve Loan Long loanId = applyAndApproveLoan(clientId, loanProductId, "25 December 2022", 1500.0, 3, @@ -322,6 +323,48 @@ public void testValidationErrorIsThrownWhenCreatingActionThatOverlaps() { }); } + @Test + public void testLoanAndInstallmentDelinquencyCalculationForCOBAfterPausePeriodEndTest() { + runAt("01 November 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith25PctDownPaymentAndDelinquencyBucket(true, true, true, 0); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 November 2023", 1000.0, 3, req -> { + req.submittedOnDate("01 November 2023"); + req.setLoanTermFrequency(45); + req.setRepaymentEvery(15); + req.setGraceOnArrearsAgeing(0); + }); + + // Partial Loan amount Disbursement + disburseLoan(loanId, BigDecimal.valueOf(100.00), "01 November 2023"); + + // Update business date + businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BUSINESS_DATE.getName()).date("05 November 2023") + .dateFormat(DATETIME_PATTERN).locale("en")); + + // Create Delinquency Pause for the Loan + PostLoansDelinquencyActionResponse response = loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, + "16 November 2023", "25 November 2023"); + + // run cob for business date 26 November + final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); + businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BUSINESS_DATE.getName()).date("26 November 2023") + .dateFormat(DATETIME_PATTERN).locale("en")); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue())); + + // Loan delinquency data + verifyLoanDelinquencyData(loanId, 1, new InstallmentDelinquencyData(1, 3, BigDecimal.valueOf(25.0))); + + // Validate Delinquency Pause Period on Loan + validateLoanDelinquencyPausePeriods(loanId, pausePeriods("16 November 2023", "25 November 2023", false)); + }); + } + private void validateLoanDelinquencyPausePeriods(Long loanId, GetLoansLoanIdDelinquencyPausePeriod... pausePeriods) { GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); Assertions.assertNotNull(loan.getDelinquent()); @@ -388,7 +431,7 @@ private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnable } private Long createLoanProductWith25PctDownPaymentAndDelinquencyBucket(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled, - boolean installmentLevelDelinquencyEnabled) { + boolean installmentLevelDelinquencyEnabled, Integer graceOnArrearsAging) { // Create DelinquencyBuckets Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, List.of(// Pair.of(1, 3), // @@ -400,6 +443,7 @@ private Long createLoanProductWith25PctDownPaymentAndDelinquencyBucket(boolean a product.setDelinquencyBucketId(delinquencyBucketId.longValue()); product.setMultiDisburseLoan(multiDisburseEnabled); product.setEnableDownPayment(true); + product.setGraceOnArrearsAgeing(graceOnArrearsAging); product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE); product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java index 3dd2c5586b6..b8898ce0025 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java @@ -21,6 +21,7 @@ import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE; import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -109,8 +110,10 @@ public void testCreateDelinquencyRanges() { // then assertNotNull(delinquencyRangeResponse01); assertNotNull(ranges); - assertEquals(1, ranges.get(0).getMinimumAgeDays(), "Expected Min Age Days to 1"); - assertEquals(3, ranges.get(0).getMaximumAgeDays(), "Expected Max Age Days to 3"); + assertFalse(ranges.isEmpty()); + GetDelinquencyRangesResponse range = ranges.get(ranges.size() - 1); + assertEquals(1, range.getMinimumAgeDays(), "Expected Min Age Days to 1"); + assertEquals(3, range.getMaximumAgeDays(), "Expected Max Age Days to 3"); } @Test diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/IdempotencyTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/IdempotencyTest.java index ee2b2c1a646..70e7534975d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/IdempotencyTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/IdempotencyTest.java @@ -19,22 +19,25 @@ package org.apache.fineract.integrationtests; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; import io.restassured.response.Response; +import io.restassured.response.ResponseBody; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import org.apache.fineract.cob.data.BusinessStep; import org.apache.fineract.cob.data.JobBusinessStepConfigData; import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; import org.apache.fineract.integrationtests.common.IdempotencyHelper; import org.apache.fineract.integrationtests.common.Utils; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -70,9 +73,9 @@ public void shouldUpdateStepOrder() { IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); Response responseSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); - Assertions.assertEquals(response.getBody().asString(), responseSecond.getBody().asString()); - Assertions.assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertEquals(response.getBody().asString(), responseSecond.getBody().asString()); + assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); idempotencyKeyHeader = UUID.randomUUID().toString(); @@ -89,9 +92,9 @@ public void shouldUpdateStepOrder() { IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); Response updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); - Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); + assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME); applyChargeStep = newStepConfig.getBusinessSteps().stream() @@ -109,9 +112,9 @@ public void shouldUpdateStepOrder() { updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); - Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); + assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME); applyChargeStep = newStepConfig.getBusinessSteps().stream() @@ -126,14 +129,14 @@ public void shouldUpdateStepOrder() { updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, IdempotencyHelper.toJsonString(originalStepConfig.getBusinessSteps()), idempotencyKeyHeader); - Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); + assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); } @Test - public void shoudTheSecondRequestWithSameIdempotencyKeyWillFailureToo() { + public void shouldTheSecondRequestWithSameIdempotencyKeyWillFailureToo() { ResponseSpecification responseSpecForError = new ResponseSpecBuilder().expectStatusCode(400).build(); List requestBody = new ArrayList<>(); String idempotencyKey = UUID.randomUUID().toString(); @@ -141,13 +144,14 @@ public void shoudTheSecondRequestWithSameIdempotencyKeyWillFailureToo() { Response response1 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME, IdempotencyHelper.toJsonString(requestBody), idempotencyKey); - Assertions.assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - String originalBody = response1.getBody().asString(); + assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + ResponseBody body1 = response1.getBody(); + assertNotNull(body1); Response response2 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME, IdempotencyHelper.toJsonString(requestBody), idempotencyKey); - Assertions.assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); - Assertions.assertEquals(originalBody, response2.getBody().asString()); + assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); + assertEquals((Map) body1.jsonPath().get(""), response2.getBody().jsonPath().get("")); } private BusinessStep getBusinessSteps(Long order, String stepName) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java index 4cdc88609a7..34f20611fe9 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java @@ -181,8 +181,8 @@ public void feeAndPenaltyChargePaymentWithDefaultAllocationRuleTest() { new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en")); - final float feePortion = 50.0f; - final float penaltyPortion = 100.0f; + final double feePortion = 50.0d; + final double penaltyPortion = 100.0d; Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, @@ -209,8 +209,8 @@ public void feeAndPenaltyChargePaymentWithDefaultAllocationRuleTest() { assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding()); assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue()); assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding()); - assertEquals(400.0f, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod()); - assertEquals(400.0f, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod()); + assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod()); + assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod()); assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); scheduleJobHelper.executeAndAwaitJob(jobName); @@ -218,11 +218,11 @@ public void feeAndPenaltyChargePaymentWithDefaultAllocationRuleTest() { loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size()); assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue()); - assertEquals(0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding()); + assertEquals(0.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding()); assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue()); - assertEquals(0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding()); - assertEquals(400, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod()); - assertEquals(250, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod()); + assertEquals(0.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding()); + assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod()); + assertEquals(250.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod()); assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); } finally { GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWithAdvancedPaymentAllocationIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWithAdvancedPaymentAllocationIntegrationTests.java index 371cf9e9268..2d57ea6fa2a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWithAdvancedPaymentAllocationIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWithAdvancedPaymentAllocationIntegrationTests.java @@ -57,8 +57,6 @@ public class LoanWithAdvancedPaymentAllocationIntegrationTests { private static ClientHelper CLIENT_HELPER; - private static ResponseSpecification RESPONSE_SPEC; - private static RequestSpecification REQUEST_SPEC; private static Account ASSET_ACCOUNT; private static Account FEE_PENALTY_ACCOUNT; private static Account EXPENSE_ACCOUNT; @@ -69,12 +67,12 @@ public class LoanWithAdvancedPaymentAllocationIntegrationTests { @BeforeAll public static void setupTests() { Utils.initializeRESTAssured(); - REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); - REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); - RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build(); - AccountHelper accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC); - LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC); - CLIENT_HELPER = new ClientHelper(REQUEST_SPEC, RESPONSE_SPEC); + RequestSpecification requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec); + LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(requestSpec, responseSpec); + CLIENT_HELPER = new ClientHelper(requestSpec, responseSpec); ASSET_ACCOUNT = accountHelper.createAssetAccount(); FEE_PENALTY_ACCOUNT = accountHelper.createAssetAccount(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java index 1b7faf84eef..b3480ce59e4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java @@ -18,17 +18,31 @@ */ package org.apache.fineract.integrationtests; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; -import java.util.ArrayList; -import org.apache.fineract.client.models.GetMakerCheckerResponse; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.fineract.client.models.GlobalConfigurationPropertyData; +import org.apache.fineract.client.models.PutPermissionsRequest; +import org.apache.fineract.integrationtests.common.AuditHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.commands.MakercheckersHelper; +import org.apache.fineract.integrationtests.common.organisation.StaffHelper; +import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; +import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; +import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper; +import org.apache.fineract.integrationtests.useradministration.users.UserHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,6 +52,12 @@ public class MakercheckerTest { private ResponseSpecification responseSpec; private RequestSpecification requestSpec; private MakercheckersHelper makercheckersHelper; + private RolesHelper rolesHelper; + private AuditHelper auditHelper; + private SavingsProductHelper savingsProductHelper; + private SavingsAccountHelper savingsAccountHelper; + private static final String START_DATE_STRING = "03 June 2023"; + private static final String TRANSACTION_DATE_STRING = "05 June 2023"; @BeforeEach public void setup() { @@ -46,14 +66,151 @@ public void setup() { this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); this.makercheckersHelper = new MakercheckersHelper(this.requestSpec, this.responseSpec); + this.rolesHelper = new RolesHelper(); + this.auditHelper = new AuditHelper(requestSpec, responseSpec); + this.savingsProductHelper = new SavingsProductHelper(); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); } @Test - public void testMakerchekerInboxList() { + public void testMakercheckerInboxList() { // given // when - final ArrayList makerCheckerList = this.makercheckersHelper.getMakerCheckerList(); - + List> makerCheckerList = this.makercheckersHelper.getMakerCheckerList(null); assertNotNull(makerCheckerList); } + + @Test + public void testMakerCheckerOn() { + GlobalConfigurationPropertyData mcConfig = GlobalConfigurationHelper.getGlobalConfigurationByName(requestSpec, responseSpec, + "maker-checker"); + Long mcConfigId = mcConfig.getId(); + boolean mcConfigUpdate = false; + if (!Boolean.TRUE.equals(mcConfig.getEnabled())) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, mcConfigId, true); + mcConfigUpdate = true; + } + GlobalConfigurationPropertyData sameMcConfig = GlobalConfigurationHelper.getGlobalConfigurationByName(requestSpec, responseSpec, + "enable-same-maker-checker"); + Long sameMcConfigId = mcConfig.getId(); + boolean sameMcConfigUpdate = false; + if (Boolean.TRUE.equals(sameMcConfig.getEnabled())) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, sameMcConfigId, false); + sameMcConfigUpdate = true; + } + + try { + // client permission - maker-checker disabled + PutPermissionsRequest putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("CREATE_CLIENT", false); + rolesHelper.updatePermissions(putPermissionsRequest); + putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("ACTIVATE_CLIENT", false); + rolesHelper.updatePermissions(putPermissionsRequest); + + Integer roleId = RolesHelper.createRole(requestSpec, responseSpec); + Map permissionMap = Map.of("CREATE_CLIENT", true, "CREATE_CLIENT_CHECKER", true, "ACTIVATE_CLIENT", true, + "ACTIVATE_CLIENT_CHECKER", true, "WITHDRAWAL_SAVINGSACCOUNT", true, "WITHDRAWAL_SAVINGSACCOUNT_CHECKER", true); + RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap); + final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec); + // create maker user + String maker = Utils.uniqueRandomStringGenerator("user", 8); + final Integer makerUserId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, maker, + "P4ssw0rd", "resourceId"); + + // create client - maker-checker disabled + RequestSpecification makerRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build() + .header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(maker, "P4ssw0rd")); + Integer clientId = ClientHelper.createClient(makerRequestSpec, this.responseSpec); + assertNotNull(clientId); + ClientHelper.verifyClientCreatedOnServer(requestSpec, this.responseSpec, clientId); + + final Integer savingsId = createApproveActivateSavingsAccountDailyPosting(clientId, START_DATE_STRING); + assertNotNull(savingsId); + Integer transactionId = (Integer) savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", TRANSACTION_DATE_STRING, + CommonConstants.RESPONSE_RESOURCE_ID); + assertNotNull(transactionId); + + // client and saving permission - maker-checker enabled + putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("ACTIVATE_CLIENT", true); + rolesHelper.updatePermissions(putPermissionsRequest); + putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("WITHDRAWAL_SAVINGSACCOUNT", true); + rolesHelper.updatePermissions(putPermissionsRequest); + + // create client - maker-checker enabled + clientId = ClientHelper.createClient(makerRequestSpec, this.responseSpec); + assertNull(clientId, "Client is created on the server"); + + List> auditDetails = makercheckersHelper + .getMakerCheckerList(Map.of("actionName", "CREATE", "entityName", "CLIENT", "makerId", makerUserId.toString())); + assertEquals(1, auditDetails.size(), "More than one command exists"); + Long clientCommandId = ((Double) auditDetails.get(0).get("id")).longValue(); + + // savings withdrawal - maker-checker enabled + SavingsAccountHelper makerSavingsHelper = new SavingsAccountHelper(makerRequestSpec, this.responseSpec); + Integer withdrawalId = (Integer) makerSavingsHelper.withdrawalFromSavingsAccount(savingsId, "100", TRANSACTION_DATE_STRING, + CommonConstants.RESPONSE_RESOURCE_ID); + assertNull(withdrawalId, "Withdrawal performed on the server"); + + auditDetails = makercheckersHelper.getMakerCheckerList( + Map.of("actionName", "WITHDRAWAL", "entityName", "SAVINGSACCOUNT", "makerId", makerUserId.toString())); + assertEquals(1, auditDetails.size(), "More than one command exists"); + Long savingCommandId = ((Double) auditDetails.get(0).get("id")).longValue(); + + // check by the same user should fail + ResponseSpecification failedResponseSpec = new ResponseSpecBuilder().expectStatusCode(400).build(); + MakercheckersHelper.approveMakerCheckerEntry(makerRequestSpec, failedResponseSpec, clientCommandId); + MakercheckersHelper.approveMakerCheckerEntry(makerRequestSpec, failedResponseSpec, savingCommandId); + + // create checker user + String checker = Utils.uniqueRandomStringGenerator("user", 8); + final Integer checkerUserId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, checker, + "P4ssw0rd", "resourceId"); + RequestSpecification checkerRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build() + .header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(checker, "P4ssw0rd")); + + // check by another checker user should succeed + HashMap response = MakercheckersHelper.approveMakerCheckerEntry(checkerRequestSpec, responseSpec, clientCommandId); + assertNotNull(response); + clientId = (Integer) response.get("clientId"); + assertNotNull(clientId); + ClientHelper.verifyClientCreatedOnServer(requestSpec, responseSpec, clientId); + + response = MakercheckersHelper.approveMakerCheckerEntry(checkerRequestSpec, responseSpec, savingCommandId); + assertNotNull(response); + withdrawalId = (Integer) response.get("resourceId"); + assertNotNull(withdrawalId); + + // add checker superuser permission - actions are performed in one step + permissionMap = Map.of("CHECKER_SUPER_USER", true); + RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap); + clientId = ClientHelper.createClient(makerRequestSpec, this.responseSpec); + assertNotNull(clientId); + ClientHelper.verifyClientCreatedOnServer(requestSpec, this.responseSpec, clientId); + + withdrawalId = (Integer) makerSavingsHelper.withdrawalFromSavingsAccount(savingsId, "100", TRANSACTION_DATE_STRING, + CommonConstants.RESPONSE_RESOURCE_ID); + assertNotNull(withdrawalId); + } finally { + if (mcConfigUpdate) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, mcConfigId, false); + } + if (sameMcConfigUpdate) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, sameMcConfigId, true); + } + PutPermissionsRequest putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("WITHDRAWAL_SAVINGSACCOUNT", + false); + rolesHelper.updatePermissions(putPermissionsRequest); + } + } + + private Integer createSavingsProductDailyPosting() { + final String savingsProductJSON = this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily() + .withInterestPostingPeriodTypeAsDaily().withInterestCalculationPeriodTypeAsDailyBalance().build(); + return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); + } + + private Integer createApproveActivateSavingsAccountDailyPosting(final Integer clientID, final String startDate) { + final Integer savingsProductID = createSavingsProductDailyPosting(); + assertNotNull(savingsProductID); + return savingsAccountHelper.createApproveActivateSavingsAccount(clientID, savingsProductID, startDate); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionTest.java index b161ab00187..d643d23ff00 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionTest.java @@ -18,10 +18,11 @@ */ package org.apache.fineract.integrationtests; +import static org.apache.fineract.integrationtests.common.CommonConstants.RESPONSE_RESOURCE_ID; import static org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper.PAYMENT_TYPE_ID; import static org.apache.fineract.integrationtests.common.system.DatatableHelper.addDatatableColumn; +import static org.apache.http.HttpStatus.SC_CONFLICT; import static org.apache.http.HttpStatus.SC_FORBIDDEN; -import static org.apache.http.HttpStatus.SC_LOCKED; import static org.apache.http.HttpStatus.SC_OK; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.is; @@ -31,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Strings; import com.google.gson.Gson; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; @@ -59,7 +61,6 @@ import org.apache.fineract.integrationtests.common.BatchHelper; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.ClientHelper; -import org.apache.fineract.integrationtests.common.CommonConstants; import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; @@ -85,6 +86,7 @@ public class SavingsAccountTransactionTest { private ResponseSpecification responseSpec; private ResponseSpecification concurrentResponseSpec; + private ResponseSpecification deadlockResponseSpec; private RequestSpecification requestSpec; private SavingsProductHelper savingsProductHelper; private SavingsAccountHelper savingsAccountHelper; @@ -96,7 +98,8 @@ public void setup() { this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(SC_OK).build(); - this.concurrentResponseSpec = new ResponseSpecBuilder().expectStatusCode(anyOf(is(SC_OK), is(SC_LOCKED))).build(); + this.concurrentResponseSpec = new ResponseSpecBuilder().expectStatusCode(anyOf(is(SC_OK), is(SC_CONFLICT))).build(); + this.deadlockResponseSpec = new ResponseSpecBuilder().expectStatusCode(anyOf(is(SC_OK), is(SC_CONFLICT), is(SC_FORBIDDEN))).build(); this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); this.savingsProductHelper = new SavingsProductHelper(); this.datatableHelper = new DatatableHelper(this.requestSpec, this.responseSpec); @@ -115,7 +118,7 @@ public void verifySavingsTransactionSubmittedOnDateAndTransactionDate() throws J final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDateString); assertNotNull(clientID); - final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDateString); + final Integer savingsId = createApproveActivateSavingsAccountDailyPosting(clientID, startDateString); assertNotNull(savingsId); performSavingsTransaction(savingsId, "100", depositDate, true); @@ -173,7 +176,7 @@ public void testConcurrentSavingsBatchTransactions() { // creating datatable for client entity final HashMap columnMap = new HashMap<>(); - String datatableName = Utils.uniqueRandomStringGenerator("savings_transaction" + "_", 5).toLowerCase(); + String datatableName = Utils.uniqueRandomStringGenerator("dt_savings_transaction_", 5).toLowerCase(); columnMap.put("datatableName", datatableName); columnMap.put("apptableName", "m_savings_account_transaction"); columnMap.put("multiRow", false); @@ -190,7 +193,7 @@ public void testConcurrentSavingsBatchTransactions() { SavingsAccountHelper batchWithTransactionHelper = new SavingsAccountHelper(requestSpec, concurrentResponseSpec); SavingsAccountHelper batchWithoutTransactionHelper = new SavingsAccountHelper(requestSpec, - new ResponseSpecBuilder().expectStatusCode(anyOf(is(SC_OK), is(SC_LOCKED), is(SC_FORBIDDEN))).build()); + new ResponseSpecBuilder().expectStatusCode(anyOf(is(SC_OK), is(SC_CONFLICT), is(SC_FORBIDDEN))).build()); String transactionDate = SavingsAccountHelper.TRANSACTION_DATE; String transactionAmount = "10"; ExecutorService executor = Executors.newFixedThreadPool(30); @@ -223,7 +226,7 @@ public void testConcurrentSavingsBatchTransactions() { log.info("\nFinished all threads"); } - // @Test + @Test public void testDeadlockSavingsBatchTransactions() { final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec); ClientHelper.verifyClientCreatedOnServer(requestSpec, responseSpec, clientID); @@ -239,7 +242,7 @@ public void testDeadlockSavingsBatchTransactions() { savingsAccountHelper.approveSavings(savingsId2); savingsAccountHelper.activateSavings(savingsId2); - SavingsAccountHelper batchWithTransactionHelper = new SavingsAccountHelper(requestSpec, concurrentResponseSpec); + SavingsAccountHelper batchWithTransactionHelper = new SavingsAccountHelper(requestSpec, deadlockResponseSpec); String transactionDate = SavingsAccountHelper.TRANSACTION_DATE; String transactionAmount = "10"; @@ -281,10 +284,9 @@ private void enableBusinessDate(RequestSpecification requestSpec, ResponseSpecif private void performSavingsTransaction(Integer savingsId, String amount, LocalDate transactionDate, boolean isDeposit) { String transactionType = isDeposit ? "Deposit" : "Withdrawal"; Integer transactionId = isDeposit - ? (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, amount, depositDateString, - CommonConstants.RESPONSE_RESOURCE_ID) + ? (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, amount, depositDateString, RESPONSE_RESOURCE_ID) : (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, amount, withdrawDateString, - CommonConstants.RESPONSE_RESOURCE_ID); + RESPONSE_RESOURCE_ID); assertNotNull(transactionId); @@ -293,11 +295,12 @@ private void performSavingsTransaction(Integer savingsId, String amount, LocalDa assertEquals(transactionId, (Integer) transaction.get("id"), "Check Savings " + transactionType + " Transaction"); LocalDate transactionDateFromResponse = extractLocalDate(transaction, "date"); - assertTrue(DateUtils.isEqual(transactionDate, transactionDateFromResponse), - "Transaction Date check for Savings " + transactionType + " Transaction"); - LocalDate submittedOnDate = extractLocalDate(transaction, "submittedOnDate"); - assertTrue(DateUtils.isEqual(submittedOnDate, Utils.getLocalDateOfTenant()), - "Submitted On Date check for Savings " + transactionType + " Transaction"); + assertTrue(DateUtils.isEqual(transactionDate, transactionDateFromResponse), "Transaction Date check for Savings " + transactionType + + " Transaction. Expected: " + transactionDate + ", current: " + transactionDateFromResponse); + LocalDate submittedOnDate = Utils.getLocalDateOfTenant(); + LocalDate submittedOnDateFromResponse = extractLocalDate(transaction, "submittedOnDate"); + assertTrue(DateUtils.isEqual(submittedOnDate, submittedOnDateFromResponse), "Submitted On Date check for Savings " + transactionType + + " Transaction. Expected: " + submittedOnDate + ", current: " + submittedOnDateFromResponse); } private LocalDate extractLocalDate(HashMap transactionMap, String fieldName) { @@ -307,17 +310,10 @@ private LocalDate extractLocalDate(HashMap transactionMap, String fieldName) { return extractedDate; } - private Integer createSavingsAccountDailyPosting(final Integer clientID, final String startDate) { + private Integer createApproveActivateSavingsAccountDailyPosting(final Integer clientID, final String startDate) { final Integer savingsProductID = createSavingsProductDailyPosting(); assertNotNull(savingsProductID); - final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientID, savingsProductID, - ACCOUNT_TYPE_INDIVIDUAL, startDate); - assertNotNull(savingsId); - HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavingsOnDate(savingsId, startDate); - SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); - savingsStatusHashMap = this.savingsAccountHelper.activateSavingsAccount(savingsId, startDate); - SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); - return savingsId; + return savingsAccountHelper.createApproveActivateSavingsAccount(clientID, savingsProductID, startDate); } private Integer createSavingsProductDailyPosting() { @@ -378,28 +374,31 @@ public void run() { final List responses = enclosingTransaction ? BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json) : BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, responseSpec, json); - assertNotNull(responses); + assertNotNull(responses, "Responses"); if (enclosingTransaction) { Integer statusCode1 = responses.get(0).getStatusCode(); - assertNotNull(statusCode1); - assertTrue(SC_OK == statusCode1 || SC_LOCKED == statusCode1); + assertNotNull(statusCode1, "First enlosingTransaction response status code"); + assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1, "Status code: " + statusCode1); if (SC_OK == statusCode1) { - assertEquals(4, responses.size()); + assertEquals(4, responses.size(), "Response size for enlosingTransaction OK response"); Integer statusCode4 = responses.get(3).getStatusCode(); - assertNotNull(statusCode4); - assertEquals(SC_OK, statusCode4); + assertNotNull(statusCode4, "Last enlosingTransaction OK response status code"); + assertEquals(SC_OK, statusCode4, "Last enlosingTransaction OK response status code"); } else { - assertEquals(1, responses.size()); + assertEquals(1, responses.size(), "Response size for enlosingTransaction failed response"); } } else { - assertEquals(4, responses.size()); + assertEquals(4, responses.size(), "Response size for without-enlosingTransaction response"); Integer statusCode1 = responses.get(0).getStatusCode(); - assertNotNull(statusCode1); - assertTrue(SC_OK == statusCode1 || SC_LOCKED == statusCode1); + assertNotNull(statusCode1, "First without-enlosingTransaction response status code"); + assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1, + "First without-enlosingTransaction response status code: " + statusCode1); Integer statusCode4 = responses.get(3).getStatusCode(); - assertNotNull(statusCode4); - assertTrue(SC_OK == statusCode1 ? (SC_OK == statusCode4 || SC_LOCKED == statusCode4) - : (SC_FORBIDDEN == statusCode4 || SC_LOCKED == statusCode4)); + assertNotNull(statusCode4, "Last without-enlosingTransaction response status code"); + assertTrue( + SC_OK == statusCode1 ? (SC_OK == statusCode4 || SC_CONFLICT == statusCode4) + : (SC_FORBIDDEN == statusCode4 || SC_CONFLICT == statusCode4), + "Last without-enlosingTransaction response status code: " + statusCode4); } } else { String json = transactionData.getJson(); @@ -413,14 +412,14 @@ public void run() { } private static boolean checkConcurrentResponse(String response) { - assertNotNull(response); + assertNotNull(response, "Single response"); JsonPath res = JsonPath.from(response); - String statusCode = (String) res.get("httpStatusCode"); + String statusCode = res.get("httpStatusCode"); if (statusCode == null) { - assertNotNull(res.get(CommonConstants.RESPONSE_RESOURCE_ID)); + assertNotNull(res.get(RESPONSE_RESOURCE_ID), "Single response " + RESPONSE_RESOURCE_ID); return true; } - assertEquals(String.valueOf(SC_LOCKED), statusCode); + assertEquals(String.valueOf(SC_CONFLICT), statusCode, "Single response status code"); return false; } } @@ -435,17 +434,22 @@ private void runDeadlockBatch(SavingsAccountHelper savingsHelper, Integer saving RequestSpecification requestSpec = savingsHelper.getRequestSpec(); ResponseSpecification responseSpec = savingsHelper.getResponseSpec(); final List responses = BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json); - assertNotNull(responses); - Integer statusCode = responses.get(0).getStatusCode(); - assertNotNull(statusCode); - assertTrue(SC_OK == statusCode || SC_LOCKED == statusCode); + assertNotNull(responses, "Responses"); + BatchResponse response1 = responses.get(0); + Integer statusCode = response1.getStatusCode(); + String msg = Strings.nullToEmpty(response1.getBody()); + assertNotNull(statusCode, "First response status code"); + assertTrue( + SC_OK == statusCode || SC_CONFLICT == statusCode + || (SC_FORBIDDEN == statusCode && msg.contains("Cannot add or update a child row")), + "Status code: " + statusCode + ", message: " + msg); if (SC_OK == statusCode) { - assertEquals(4, responses.size()); + assertEquals(4, responses.size(), "Response size for OK response"); Integer statusCode4 = responses.get(3).getStatusCode(); - assertNotNull(statusCode4); - assertEquals(SC_OK, statusCode4); + assertNotNull(statusCode4, "Last OK response status code"); + assertEquals(SC_OK, statusCode4, "Last OK response status code"); } else { - assertEquals(1, responses.size()); + assertEquals(1, responses.size(), "Response size for failed response"); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java index eb410a05dbd..166019e814e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java @@ -339,43 +339,205 @@ public void testApplyHolidaysToLoansJobOutcome() throws InterruptedException { HashMap holidayData = HolidayHelper.getHolidayById(requestSpec, responseSpec, holidayId.toString()); ArrayList repaymentsRescheduledDate = (ArrayList) holidayData.get("repaymentsRescheduledTo"); + Assertions.assertNotNull(repaymentsRescheduledDate); // Loan Repayment Schedule Before Apply Holidays To Loans - LinkedHashMap repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule"); - ArrayList periods = (ArrayList) repaymentScheduleHashMap.get("periods"); - - for (LinkedHashMap period : periods) { - ArrayList fromDate = (ArrayList) period.get("fromDate"); - if (fromDate != null && Objects.equals(fromDate.get(1), repaymentsRescheduledDate.get(1))) { - Assertions.assertNotEquals(repaymentsRescheduledDate.get(2), fromDate.get(2), - "Verifying Repayment Rescheduled Day before Running Apply Holidays to Loans Scheduler Job"); + final LinkedHashMap repaymentScheduleHashMapBeforeHolidaysApply = JsonPath.from(loanDetails).get("repaymentSchedule"); + final ArrayList periodsBeforeHolidaysApply = (ArrayList) repaymentScheduleHashMapBeforeHolidaysApply + .get("periods"); + + for (LinkedHashMap period : periodsBeforeHolidaysApply) { + final ArrayList fromDate = (ArrayList) period.get("fromDate"); + if (fromDate != null) { + final Integer fromDateMonth = fromDate.get(1); + final Integer repaymentsRescheduledDateMonth = repaymentsRescheduledDate.get(1); + if (Objects.equals(fromDateMonth, repaymentsRescheduledDateMonth)) { + final Integer repaymentsRescheduledDateDay = repaymentsRescheduledDate.get(2); + final Integer fromDateDay = fromDate.get(2); + Assertions.assertNotEquals(repaymentsRescheduledDateDay, fromDateDay, + "Verifying Repayment Rescheduled Day before Running Apply Holidays to Loans Scheduler Job"); + } } } - String JobName = "Apply Holidays To Loans"; + String jobName = "Apply Holidays To Loans"; - this.schedulerJobHelper.executeAndAwaitJob(JobName); + this.schedulerJobHelper.executeAndAwaitJob(jobName); // Loan Repayment Schedule After Apply Holidays To Loans loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); - repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule"); - periods = (ArrayList) repaymentScheduleHashMap.get("periods"); + final LinkedHashMap repaymentScheduleHashMapAfterHolidaysApply = JsonPath.from(loanDetails).get("repaymentSchedule"); + final ArrayList periodsAfterHolidaysApply = (ArrayList) repaymentScheduleHashMapAfterHolidaysApply + .get("periods"); ArrayList dateToApplyHolidays = null; - for (LinkedHashMap period : periods) { - ArrayList fromDate = (ArrayList) period.get("fromDate"); - if (fromDate != null && Objects.equals(fromDate.get(1), repaymentsRescheduledDate.get(1))) { - dateToApplyHolidays = fromDate; + for (LinkedHashMap periodBefore : periodsBeforeHolidaysApply) { + for (LinkedHashMap periodAfter : periodsAfterHolidaysApply) { + final ArrayList fromDateBefore = (ArrayList) periodBefore.get("fromDate"); + final ArrayList fromDateAfter = (ArrayList) periodAfter.get("fromDate"); + + if (fromDateBefore != null && fromDateAfter != null) { + final Integer fromDateMonthBefore = fromDateBefore.get(1); + final Integer fromDateMonthAfter = fromDateAfter.get(1); + final Integer repaymentsRescheduledDateMonth = repaymentsRescheduledDate.get(1); + + if (Objects.equals(fromDateMonthAfter, repaymentsRescheduledDateMonth)) { + dateToApplyHolidays = fromDateAfter; + } else if (Objects.equals(fromDateMonthAfter, fromDateMonthBefore)) { + assertEqualDay(fromDateBefore, fromDateAfter, + "Verifying Repayment Scheduled Days Before And After Running Apply Holidays to Loans Scheduler Job Are Equals"); + } + } } } Assertions.assertNotNull(dateToApplyHolidays); - Assertions.assertEquals(repaymentsRescheduledDate.get(0), dateToApplyHolidays.get(0), - "Verifying Repayment Rescheduled Year after Running Apply Holidays to Loans Scheduler Job"); - Assertions.assertEquals(repaymentsRescheduledDate.get(2), dateToApplyHolidays.get(2), + assertEqualDay(repaymentsRescheduledDate, dateToApplyHolidays, "Verifying Repayment Rescheduled Day after Running Apply Holidays to Loans Scheduler Job"); } + private void assertEqualDay(ArrayList fromDateBefore, ArrayList fromDateAfter, String message) { + Integer fromDateDayBefore = fromDateBefore.get(2); + Integer fromDateDayAfter = fromDateAfter.get(2); + Assertions.assertEquals(fromDateDayBefore, fromDateDayAfter, message); + } + + @Test + public void testApplyType1HolidaysToLoansJobOutcome() throws InterruptedException { + this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec); + Assertions.assertNotNull(clientID); + + Integer holidayId = HolidayHelper.createTyoe1Holidays(requestSpec, responseSpec); + Assertions.assertNotNull(holidayId); + + final Integer loanProductID = createLoanProduct(null); + Assertions.assertNotNull(loanProductID); + + final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "04 January 2024"); + Assertions.assertNotNull(loanID); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = this.loanTransactionHelper.approveLoan("04 January 2024", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); + loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("04 January 2024", loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + // Retrieving All Global Configuration details + final ArrayList globalConfig = GlobalConfigurationHelper.getAllGlobalConfigurations(requestSpec, responseSpec); + Assertions.assertNotNull(globalConfig); + + // Updating Value for reschedule-repayments-on-holidays Global + // Configuration + Integer configId = (Integer) globalConfig.get(3).get("id"); + Assertions.assertNotNull(configId); + + HashMap configData = GlobalConfigurationHelper.getGlobalConfigurationById(requestSpec, responseSpec, configId.toString()); + Assertions.assertNotNull(configData); + + Boolean enabled = (Boolean) globalConfig.get(3).get("enabled"); + + if (!enabled) { + enabled = true; + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configId, enabled); + } + + holidayId = HolidayHelper.activateHolidays(requestSpec, responseSpec, holidayId.toString()); + Assertions.assertNotNull(holidayId); + + HashMap holidayData = HolidayHelper.getHolidayById(requestSpec, responseSpec, holidayId.toString()); + + LinkedHashMap repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule"); + ArrayList periods = (ArrayList) repaymentScheduleHashMap.get("periods"); + String JobName = "Apply Holidays To Loans"; + + this.schedulerJobHelper.executeAndAwaitJob(JobName); + + // Loan Repayment Schedule After Apply Holidays To Loans + loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); + repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule"); + ArrayList periodsAfterRescheduleApplied = (ArrayList) repaymentScheduleHashMap.get("periods"); + + ArrayList fromDateValues = (ArrayList) periods.get(1).get("fromDate"); + LocalDate fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + ArrayList dueDateValues = (ArrayList) periods.get(1).get("dueDate"); + LocalDate dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 1, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periods.get(2).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periods.get(2).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periods.get(3).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periods.get(3).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 4, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periods.get(4).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periods.get(4).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 4, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 5, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(1).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(1).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 1, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(2).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(2).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(3).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(3).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 5, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(4).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(4).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 5, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 6, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + } + @Test public void testApplyDueFeeChargesForSavingsJobOutcome() throws InterruptedException { this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/StaffTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/StaffTest.java index eb83bd55fee..cbbb19dd0d7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/StaffTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/StaffTest.java @@ -19,6 +19,7 @@ package org.apache.fineract.integrationtests; import com.google.gson.Gson; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; @@ -35,6 +36,7 @@ import org.junit.jupiter.api.Test; @Deprecated // TODO move this into new org.apache.fineract.integrationtests.client.StaffTest +@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") public class StaffTest { private RequestSpecification requestSpec; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/AuditHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/AuditHelper.java index 48687b48ee6..526028b7c09 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/AuditHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/AuditHelper.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.google.gson.Gson; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.util.Collections; @@ -27,6 +28,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import org.apache.fineract.client.util.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,13 +40,15 @@ public class AuditHelper { - private ResponseSpecification responseSpec; - private RequestSpecification requestSpec; - private static final Logger LOG = LoggerFactory.getLogger(AuditHelper.class); private static final String AUDIT_BASE_URL = "/fineract-provider/api/v1/audits?" + Utils.TENANT_IDENTIFIER; private static final String AUDITSEARCH_BASE_URL = "/fineract-provider/api/v1/audits/searchtemplate?" + Utils.TENANT_IDENTIFIER; + private static final Gson GSON = new JSON().getGson(); + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + public AuditHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { this.requestSpec = requestSpec; this.responseSpec = responseSpec; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java index 4fe95872769..a44b571fbbd 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java @@ -119,8 +119,8 @@ public static void verifyAllDefaultGlobalConfigurations(final RequestSpecificati ArrayList expectedGlobalConfigurations = getAllDefaultGlobalConfigurations(); ArrayList actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec); - Assertions.assertEquals(52, expectedGlobalConfigurations.size()); - Assertions.assertEquals(52, actualGlobalConfigurations.size()); + Assertions.assertEquals(53, expectedGlobalConfigurations.size()); + Assertions.assertEquals(53, actualGlobalConfigurations.size()); for (int i = 0; i < expectedGlobalConfigurations.size(); i++) { @@ -571,9 +571,16 @@ private static ArrayList getAllDefaultGlobalConfigurations() { assetExternalizationOfNonActiveLoans.put("value", 0); assetExternalizationOfNonActiveLoans.put("enabled", true); assetExternalizationOfNonActiveLoans.put("trapDoor", false); - assetExternalizationOfNonActiveLoans.put("string_value", "due-date"); defaults.add(assetExternalizationOfNonActiveLoans); + HashMap enableSameMakerChecker = new HashMap<>(); + enableSameMakerChecker.put("id", 58); + enableSameMakerChecker.put("name", "enable-same-maker-checker"); + enableSameMakerChecker.put("value", 0); + enableSameMakerChecker.put("enabled", false); + enableSameMakerChecker.put("trapDoor", false); + defaults.add(enableSameMakerChecker); + return defaults; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java index 32f9a08e48f..1630575056f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java @@ -64,6 +64,25 @@ public static String getCreateHolidayDataAsJSON() { return HolidayCreateJson; } + public static String getCreateType1HolidayDataAsJSON() { + final HashMap map = new HashMap<>(); + List> offices = new ArrayList>(); + HashMap officeMap = new HashMap<>(); + officeMap.put("officeId", OFFICE_ID); + offices.add(officeMap); + + map.put("offices", offices); + map.put("locale", "en"); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("name", Utils.uniqueRandomStringGenerator("HOLIDAY_", 5)); + map.put("fromDate", "04 April 2024"); + map.put("toDate", "04 April 2024"); + map.put("reschedulingType", 1); + String HolidayCreateJson = new Gson().toJson(map); + LOG.info("{}", HolidayCreateJson); + return HolidayCreateJson; + } + public static String getActivateHolidayDataAsJSON() { final HashMap map = new HashMap<>(); String activateHoliday = new Gson().toJson(map); @@ -75,6 +94,10 @@ public static Integer createHolidays(final RequestSpecification requestSpec, fin return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateHolidayDataAsJSON(), "resourceId"); } + public static Integer createTyoe1Holidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { + return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateType1HolidayDataAsJSON(), "resourceId"); + } + public static Integer activateHolidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String holidayID) { final String ACTIVATE_HOLIDAY_URL = HOLIDAYS_URL + "/" + holidayID + "?command=activate&" + Utils.TENANT_IDENTIFIER; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java index 27996ce4598..5a36b99934a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java @@ -23,8 +23,9 @@ import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.lang.reflect.Type; -import java.util.ArrayList; -import org.apache.fineract.client.models.GetMakerCheckerResponse; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.fineract.client.util.JSON; import org.apache.fineract.integrationtests.common.Utils; @@ -32,7 +33,7 @@ public class MakercheckersHelper { private static final Gson GSON = new JSON().getGson(); - private static final String MAKERCHECKER_URL = "/fineract-provider/api/v1/makercheckers?" + Utils.TENANT_IDENTIFIER; + private static final String MAKERCHECKER_URL = "/fineract-provider/api/v1/makercheckers"; private final RequestSpecification requestSpec; private final ResponseSpecification responseSpec; @@ -42,10 +43,25 @@ public MakercheckersHelper(final RequestSpecification requestSpec, final Respons this.responseSpec = responseSpec; } - public ArrayList getMakerCheckerList() { - final String response = Utils.performServerGet(this.requestSpec, this.responseSpec, MAKERCHECKER_URL); - Type makerCheckerList = new TypeToken>() {}.getType(); + public List> getMakerCheckerList(Map queryParams) { + StringBuilder url = new StringBuilder(MAKERCHECKER_URL).append("?").append(Utils.TENANT_IDENTIFIER); + if (queryParams != null) { + for (Map.Entry entry : queryParams.entrySet()) { + url.append("&").append(entry.getKey()).append("=").append(entry.getValue()); + } + } + final String response = Utils.performServerGet(this.requestSpec, this.responseSpec, url.toString()); + Type makerCheckerList = new TypeToken>>() {}.getType(); return GSON.fromJson(response, makerCheckerList); } + public void approveMakerCheckerEntry(Long auditId) { + approveMakerCheckerEntry(this.requestSpec, this.responseSpec, auditId); + } + + public static HashMap approveMakerCheckerEntry(RequestSpecification requestSpec, ResponseSpecification responseSpec, + Long auditId) { + String url = MAKERCHECKER_URL + "/" + auditId + "?command=approve&" + Utils.TENANT_IDENTIFIER; + return Utils.performServerPost(requestSpec, responseSpec, url, "", ""); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index a0b7d9cfff6..98b60457421 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -20,6 +20,7 @@ import static org.apache.fineract.integrationtests.common.Utils.DEFAULT_TENANT; import static org.apache.fineract.integrationtests.common.Utils.TENANT_PARAM_NAME; +import static org.junit.jupiter.api.Assertions.assertNotNull; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; @@ -174,6 +175,16 @@ public Object applyForSavingsApplicationWithFailure(final Integer id, final Inte savingsApplicationJSON, responseAttribute); } + public Integer createApproveActivateSavingsAccount(final Integer clientId, Integer savingsProductId, final String startDate) { + final Integer savingsId = applyForSavingsApplicationOnDate(clientId, savingsProductId, ACCOUNT_TYPE_INDIVIDUAL, startDate); + assertNotNull(savingsId); + HashMap savingsStatusHashMap = approveSavingsOnDate(savingsId, startDate); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + savingsStatusHashMap = activateSavingsAccount(savingsId, startDate); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + return savingsId; + } + public HashMap updateSavingsAccount(final Integer id, final Integer savingsProductID, final Integer savingsId, final String accountType) { final String savingsApplicationJSON = new SavingsApplicationTestBuilder() // diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java index c019275fa9c..f71cc48a265 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/roles/RolesHelper.java @@ -19,24 +19,36 @@ package org.apache.fineract.integrationtests.useradministration.roles; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; +import java.lang.reflect.Type; import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.fineract.client.models.CommandProcessingResult; +import org.apache.fineract.client.models.PutPermissionsRequest; +import org.apache.fineract.client.util.JSON; +import org.apache.fineract.integrationtests.client.IntegrationTest; import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.useradministration.data.PermissionData; -public final class RolesHelper { +public final class RolesHelper extends IntegrationTest { public static final long SUPER_USER_ROLE_ID = 1L; // This is hardcoded into the initial Liquibase migration - private RolesHelper() { + public RolesHelper() { } private static final String CREATE_ROLE_URL = "/fineract-provider/api/v1/roles?" + Utils.TENANT_IDENTIFIER; private static final String ROLE_URL = "/fineract-provider/api/v1/roles"; + private static final String PERMISSIONS_URL = "/fineract-provider/api/v1/permissions"; private static final String DISABLE_ROLE_COMMAND = "disable"; private static final String ENABLE_ROLE_COMMAND = "enable"; + private static final Gson GSON = new JSON().getGson(); + public static Integer createRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { return Utils.performServerPost(requestSpec, responseSpec, CREATE_ROLE_URL, getTestCreateRoleAsJSON(), "resourceId"); } @@ -70,13 +82,25 @@ public static Integer deleteRole(final RequestSpecification requestSpec, final R } public static String addPermissionsToRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final Integer roleId, final HashMap permissionMap) { + final Integer roleId, final Map permissionMap) { return Utils.performServerPut(requestSpec, responseSpec, ROLE_URL + "/" + roleId + "/permissions?" + Utils.TENANT_IDENTIFIER, getAddPermissionsToRoleJSON(permissionMap)); } - private static String getAddPermissionsToRoleJSON(HashMap permissionMap) { - final HashMap> map = new HashMap<>(); + public static List getPermissions(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + boolean makerCheckerable) { + String response = Utils.performServerGet(requestSpec, responseSpec, + PERMISSIONS_URL + "?" + makerCheckerable + "=" + makerCheckerable); + final Type listType = new TypeToken>() {}.getType(); + return GSON.fromJson(response, listType); + } + + public CommandProcessingResult updatePermissions(PutPermissionsRequest request) { + return ok(fineract().permissions.updatePermissionsDetails(request)); + } + + private static String getAddPermissionsToRoleJSON(Map permissionMap) { + final HashMap> map = new HashMap<>(); map.put("permissions", permissionMap); return new Gson().toJson(map); } @@ -84,5 +108,4 @@ private static String getAddPermissionsToRoleJSON(HashMap permi private static String createRoleOperationURL(final String command, final Integer roleId) { return ROLE_URL + "/" + roleId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER; } - } diff --git a/kubernetes/fineractmysql-deployment.yml b/kubernetes/fineractmysql-deployment.yml index f4c67092056..582e3d2002a 100644 --- a/kubernetes/fineractmysql-deployment.yml +++ b/kubernetes/fineractmysql-deployment.yml @@ -86,7 +86,7 @@ spec: tier: fineractmysql spec: containers: - - image: mariadb:10.9 + - image: mariadb:11.2 name: mysql resources: requests: diff --git a/oauth2-tests/dependencies.gradle b/oauth2-tests/dependencies.gradle index d05ea664246..d1041fe3096 100644 --- a/oauth2-tests/dependencies.gradle +++ b/oauth2-tests/dependencies.gradle @@ -20,7 +20,7 @@ dependencies { // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // - tomcat 'org.apache.tomcat:tomcat:10.1.16@zip' + tomcat 'org.apache.tomcat:tomcat:10.1.17@zip' testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"), project(path: ':fineract-provider', configuration: 'runtimeElements'), 'org.junit.jupiter:junit-jupiter-api', diff --git a/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java b/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java index 52132dc388f..391ba7e03fa 100644 --- a/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java +++ b/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java @@ -39,7 +39,6 @@ public class OAuth2AuthenticationTest { private ResponseSpecification responseSpec; - private ResponseSpecification responseSpec403; private ResponseSpecification responseSpec401; private RequestSpecification requestSpec; private RequestSpecification requestFormSpec; @@ -61,7 +60,6 @@ public void setup() throws InterruptedException { this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); this.requestFormSpec = new RequestSpecBuilder().setContentType(ContentType.URLENC).build(); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); - this.responseSpec403 = new ResponseSpecBuilder().expectStatusCode(403).build(); this.responseSpec401 = new ResponseSpecBuilder().expectStatusCode(401).build(); } diff --git a/settings.gradle b/settings.gradle index 871f5436a06..c33ea27a86c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,8 +18,8 @@ */ plugins { - id 'com.gradle.enterprise' version '3.15.1' - id 'com.gradle.common-custom-user-data-gradle-plugin' version '1.12' + id 'com.gradle.enterprise' version '3.16.1' + id 'com.gradle.common-custom-user-data-gradle-plugin' version '1.12.1' } def isCI = System.getenv('JENKINS_URL') != null diff --git a/twofactor-tests/dependencies.gradle b/twofactor-tests/dependencies.gradle index 9e0a6e12aef..40c7f68a8cb 100644 --- a/twofactor-tests/dependencies.gradle +++ b/twofactor-tests/dependencies.gradle @@ -20,7 +20,7 @@ dependencies { // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // - tomcat 'org.apache.tomcat:tomcat:10.1.16@zip' + tomcat 'org.apache.tomcat:tomcat:10.1.17@zip' testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"), project(path: ':fineract-provider', configuration: 'runtimeElements'), 'org.junit.jupiter:junit-jupiter-api',