diff --git a/.github/workflows/build-apachecon-2024.yml b/.github/workflows/build-apachecon-2024.yml new file mode 100644 index 00000000000..db20441d993 --- /dev/null +++ b/.github/workflows/build-apachecon-2024.yml @@ -0,0 +1,56 @@ +name: Fineract Docker build - ApacheCon EU 2024 + +on: + push: + branches: + - feature/APACHE_EU_2024 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 + with: + java-version: '21' + distribution: 'zulu' + cache: gradle + + - name: Set up NodeJs 16 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 + with: + node-version: 16 + - name: Congfigure vega-cli + run: npm i -g vega-cli --unsafe + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@216d1ad2b3710bf005dc39237337b9673fd8fcd5 + - name: Install additional software + run: | + sudo apt-get update + sudo apt-get install ghostscript graphviz -y + + - name: Build Fineract + run: ./gradlew --no-daemon --console=plain clean build -x test -x cucumber -x check + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + # registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build Fineract base image + run: ./gradlew --no-daemon --console=plain :custom:docker:dockerPushImage -x test -x cucumber -x check + + - name: Build and push Fineract custom image + run: ./gradlew --no-daemon --console=plain :custom:docker:jib -x test -x cucumber -x check diff --git a/.github/workflows/build-docker-mariadb.yml b/.github/workflows/build-docker-mariadb.yml deleted file mode 100644 index cf9f08f2ff4..00000000000 --- a/.github/workflows/build-docker-mariadb.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Fineract Docker build - MariaDB - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - name: Build the image - 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 - - name: Wait for stack to come up - 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 - run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) diff --git a/.github/workflows/build-docker-postgresql.yml b/.github/workflows/build-docker-postgresql.yml deleted file mode 100644 index ed60ceaffa6..00000000000 --- a/.github/workflows/build-docker-postgresql.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Fineract Docker build - PostgreSQL - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - name: Build the image - 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 - - name: Wait for stack to come up - 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 - run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml deleted file mode 100644 index ea0caf6e83e..00000000000 --- a/.github/workflows/build-documentation.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Fineract Documentation build -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 - with: - node-version: 16 - - name: Congfigure vega-cli - run: npm i -g vega-cli --unsafe - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 - - name: Install additional software - run: | - sudo apt-get update - sudo apt-get install ghostscript graphviz -y - - - name: Documentation build - run: ./gradlew --no-daemon --console=plain doc diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml deleted file mode 100644 index 586367f0fc7..00000000000 --- a/.github/workflows/build-mariadb.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Fineract Build & Test - MariaDB -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - - services: - mariad: - image: mariadb:11.2 - ports: - - 3306:3306 - env: - MARIADB_ROOT_PASSWORD: mysql - 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.1.1 - ports: - - 9000:9000 - env: - SERVER_PORT: 9000 - JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }' - - env: - TZ: Asia/Kolkata - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 - with: - node-version: 16 - - name: Congfigure vega-cli - run: npm i -g vega-cli --unsafe - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 - - name: Verify MariaDB connection - run: | - while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do - sleep 1 - done - - name: Initialise databases - run: | - ./gradlew --no-daemon -q createDB -PdbName=fineract_tenants - ./gradlew --no-daemon -q createDB -PdbName=fineract_default - - name: Start LocalStack - env: - AWS_ENDPOINT_URL: http://localhost:4566 - AWS_ACCESS_KEY_ID: localstack - AWS_SECRET_ACCESS_KEY: localstack - AWS_REGION: us-east-1 - run: | - docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack:2.1 - sleep 10 - docker exec localstack awslocal s3api create-bucket --bucket fineract-reports - echo "LocalStack initialization complete" - - name: Install additional software - run: | - sudo apt-get update - sudo apt-get install ghostscript graphviz -y - - - name: Build & Test - env: - AWS_ENDPOINT_URL: http://localhost:4566 - AWS_ACCESS_KEY_ID: localstack - AWS_SECRET_ACCESS_KEY: localstack - AWS_REGION: us-east-1 - FINERACT_REPORT_EXPORT_S3_ENABLED: true - FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports - run: | - ./gradlew --no-daemon --console=plain build -x cucumber -x test -x doc - ./gradlew --no-daemon --console=plain cucumber -x :fineract-e2e-tests-runner:cucumber - ./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test -x :fineract-e2e-tests-runner:test - ./gradlew --no-daemon --console=plain :twofactor-tests:test - ./gradlew --no-daemon --console=plain :oauth2-tests:test - - - name: Archive test results - if: always() - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 - 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 - with: - name: server-logs - path: | - integration-tests/build/cargo/ - twofactor-tests/build/cargo/ - oauth2-tests/build/cargo/ diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml deleted file mode 100644 index 50a388633cf..00000000000 --- a/.github/workflows/build-mysql.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Fineract Build & Test - MySQL -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - - services: - mariad: - image: mysql:8.3 - ports: - - 3306:3306 - env: - MYSQL_ROOT_PASSWORD: mysql - 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.1.1 - ports: - - 9000:9000 - env: - SERVER_PORT: 9000 - JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }' - - env: - TZ: Asia/Kolkata - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 - with: - node-version: 16 - - name: Congfigure vega-cli - run: npm i -g vega-cli --unsafe - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 - - name: Verify MariaDB connection - run: | - while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do - sleep 1 - done - - name: Initialise databases - run: | - ./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_tenants - ./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_default - - name: Start LocalStack - env: - AWS_ENDPOINT_URL: http://localhost:4566 - AWS_ACCESS_KEY_ID: localstack - AWS_SECRET_ACCESS_KEY: localstack - AWS_REGION: us-east-1 - run: | - docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack:2.1 - sleep 10 - docker exec localstack awslocal s3api create-bucket --bucket fineract-reports - echo "LocalStack initialization complete" - - name: Install additional software - run: | - sudo apt-get update - sudo apt-get install ghostscript graphviz -y - - - name: Build & Test - env: - AWS_ENDPOINT_URL: http://localhost:4566 - AWS_ACCESS_KEY_ID: localstack - AWS_SECRET_ACCESS_KEY: localstack - AWS_REGION: us-east-1 - FINERACT_REPORT_EXPORT_S3_ENABLED: true - FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports - run: | - ./gradlew --no-daemon --console=plain build -x cucumber -x test -x doc - ./gradlew --no-daemon --console=plain cucumber -x :fineract-e2e-tests-runner:cucumber - ./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test :fineract-e2e-tests-runner:test -PdbType=mysql - ./gradlew --no-daemon --console=plain :twofactor-tests:test -PdbType=mysql - ./gradlew --no-daemon --console=plain :oauth2-tests:test -PdbType=mysql - - - name: Archive test results - if: always() - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 - 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 - with: - name: server-logs - path: | - integration-tests/build/cargo/ - twofactor-tests/build/cargo/ - oauth2-tests/build/cargo/ diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml deleted file mode 100644 index c5093bd6b51..00000000000 --- a/.github/workflows/build-postgresql.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Fineract Build & Test - PostgreSQL -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - - services: - postgresql: - image: postgres:16.1 - ports: - - 5432:5432 - env: - POSTGRES_USER: root - POSTGRES_PASSWORD: postgres - 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.1.1 - ports: - - 9000:9000 - env: - SERVER_PORT: 9000 - JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }' - - env: - TZ: Asia/Kolkata - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 - with: - node-version: 16 - - name: Congfigure vega-cli - run: npm i -g vega-cli --unsafe - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 - - name: Verify PostgreSQL connection - run: | - while ! pg_isready -d postgres -U root -h 127.0.0.1 -p 5432 ; do - sleep 1 - done - - name: Initialise databases - run: | - ./gradlew --no-daemon -q createPGDB -PdbName=fineract_tenants - ./gradlew --no-daemon -q createPGDB -PdbName=fineract_default - - name: Start LocalStack - env: - AWS_ENDPOINT_URL: http://localhost:4566 - AWS_ACCESS_KEY_ID: localstack - AWS_SECRET_ACCESS_KEY: localstack - AWS_REGION: us-east-1 - run: | - docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack:2.1 - sleep 10 - docker exec localstack awslocal s3api create-bucket --bucket fineract-reports - echo "LocalStack initialization complete" - - name: Install additional software - run: | - sudo apt-get update - sudo apt-get install ghostscript graphviz -y - - - name: Build & Test - env: - AWS_ENDPOINT_URL: http://localhost:4566 - AWS_ACCESS_KEY_ID: localstack - AWS_SECRET_ACCESS_KEY: localstack - AWS_REGION: us-east-1 - FINERACT_REPORT_EXPORT_S3_ENABLED: true - FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports - run: | - ./gradlew --no-daemon --console=plain build -x cucumber -x test -x doc - ./gradlew --no-daemon --console=plain cucumber -x :fineract-e2e-tests-runner:cucumber - ./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test :fineract-e2e-tests-runner:test -PdbType=postgresql - ./gradlew --no-daemon --console=plain :twofactor-tests:test -PdbType=postgresql - ./gradlew --no-daemon --console=plain :oauth2-tests:test -PdbType=postgresql - - - name: Archive test results - if: always() - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 - 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 - with: - name: server-logs - path: | - integration-tests/build/cargo/ - twofactor-tests/build/cargo/ - oauth2-tests/build/cargo/ diff --git a/.github/workflows/build-tests.yml b/.github/workflows/build-tests.yml deleted file mode 100644 index 05426773271..00000000000 --- a/.github/workflows/build-tests.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Fineract Tests - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - name: Build the image - run: ./gradlew --no-daemon --console=plain :fineract-provider:clean :fineract-provider:build :fineract-provider:jibDockerBuild -x test -x cucumber - - name: Start the Fineract stack - run: docker compose -f docker-compose-postgresql.yml up -d - - name: Wait for stack to come up - run: sleep 400 - - 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: Execute tests - env: - BASE_URL: https://localhost:8443 - TEST_USERNAME: mifos - TEST_PASSWORD: password - TEST_TENANT_ID: default - INITIALIZATION_ENABLED: true - EVENT_VERIFICATION_ENABLED: false - run: ./gradlew --no-daemon --console=plain fineract-e2e-tests-runner:cucumber --tags 'not @Skip' allureReport diff --git a/.github/workflows/smoke-activemq.yml b/.github/workflows/smoke-activemq.yml deleted file mode 100644 index 0dc0fc3fccb..00000000000 --- a/.github/workflows/smoke-activemq.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Fineract ActiveMQ Smoke - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - name: Build the image - 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 - - 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 - run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/health - - name: Check info Manager - run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) - - name: Check info Worker1 - run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/info | wc --chars) > 100 )) - - name: Run Smoke Test with Remote COB - run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest.saleActiveLoanToExternalAssetOwnerAndBuybackADayLater" -PcargoDisabled diff --git a/.github/workflows/smoke-kafka.yml b/.github/workflows/smoke-kafka.yml deleted file mode 100644 index 312bc17a21c..00000000000 --- a/.github/workflows/smoke-kafka.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Fineract Kafka Smoke - -on: [push, pull_request] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - name: Build the image - 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 - - 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 - run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/health - - name: Check info Manager - run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) - - name: Check info Worker1 - run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/info | wc --chars) > 100 )) - - name: Run Smoke Test with Remote COB - run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest.saleActiveLoanToExternalAssetOwnerAndBuybackADayLater" -PcargoDisabled diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml deleted file mode 100644 index 750626afde8..00000000000 --- a/.github/workflows/sonarqube.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Fineract Sonarqube -on: - push: - branches: - - develop - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-22.04 - env: - TZ: Asia/Kolkata - SONAR_ORGANIZATION: ${{ secrets.SONAR_ORGANIZATION }} - SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4 - with: - fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4 - with: - java-version: '17' - distribution: 'zulu' - cache: gradle - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 - - name: Sonarqube - run: ./gradlew --no-daemon --console=plain -Dsonar.verbose=true -Dsonar.login=$SONAR_TOKEN -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.projectKey=$SONAR_PROJECT_KEY --info --stacktrace sonarqube diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index eb30dda5236..00000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,26 +0,0 @@ -# https://github.com/marketplace/actions/close-stale-issues -# https://github.com/actions/stale/blob/master/action.yml -# https://github.com/actions/stale -name: Mark stale issues and pull requests -on: - schedule: - - cron: "0 0 * * *" -permissions: - contents: read - -jobs: - stale: - permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - runs-on: ubuntu-latest - steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # stale-issue-message: 'Stale issue message' - # stale-issue-label: 'no-issue-activity' - stale-pr-message: 'This pull request seems to be stale. Are you still planning to work on it? We will automatically close it in 30 days.' - stale-pr-label: 'stale' - days-before-stale: 30 - days-before-close: 30 diff --git a/build.gradle b/build.gradle index 587b01a0e3a..b4624e648b6 100644 --- a/build.gradle +++ b/build.gradle @@ -103,6 +103,8 @@ plugins { 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 + id 'com.bmuschko.docker-remote-api' version '9.4.0' apply false + id 'com.bmuschko.docker-spring-boot-application' version '9.4.0' apply false } apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle" @@ -352,6 +354,7 @@ configure(project.fineractJavaProjects) { } tasks.withType(JavaCompile) { options.compilerArgs += [ + "-parameters", "-Xlint:unchecked", "-Xlint:cast", "-Xlint:auxiliaryclass", @@ -713,6 +716,7 @@ configure(project.fineractCustomProjects) { testAnnotationProcessor('org.mapstruct:mapstruct-processor') testAnnotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor') testAnnotationProcessor('org.springframework.boot:spring-boot-configuration-processor') + testAnnotationProcessor('com.infobip:infobip-spring-data-jdbc-annotation-processor:9.0.7') } } diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle index 66bdcaea21d..a20465c4651 100644 --- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle +++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle @@ -37,6 +37,8 @@ dependencyManagement { mavenBom 'org.mockito:mockito-bom:5.10.0' mavenBom 'software.amazon.awssdk:bom:2.23.18' mavenBom 'io.github.resilience4j:resilience4j-bom:2.2.0' + mavenBom 'org.apache.camel:camel-bom:4.6.0' + mavenBom 'org.apache.camel.springboot:camel-spring-boot-bom:4.6.0' } dependencies { @@ -241,5 +243,14 @@ dependencyManagement { dependency 'org.apache.commons:commons-math3:3.6.1' dependency 'org.mockito:mockito-inline:5.2.0' + + dependency 'net.sf.jasperreports:jasperreports:6.21.3' + dependency 'net.sf.jasperreports:jasperreports-functions:6.21.3' + dependency 'net.sf.jasperreports:jasperreports-chart-themes:6.21.3' + dependency 'net.sf.jasperreports:jasperreports-chart-customizers:6.21.3' + dependency 'net.sf.jasperreports:jasperreports-custom-visualization:6.21.3' + dependency 'net.sf.jasperreports:jasperreports-annotation-processors:6.21.3' + dependency 'net.sf.jasperreports:jasperreports-metadata:6.21.3' + dependency 'net.sourceforge.dynamicreports:dynamicreports-core:6.21.3' } } diff --git a/custom/apachecon/command/core/build.gradle b/custom/apachecon/command/core/build.gradle new file mode 100644 index 00000000000..37a887db3a4 --- /dev/null +++ b/custom/apachecon/command/core/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Camel Command Processing Core' + +group = 'org.apache.fineract.apachecon.command' + +archivesBaseName = 'fineract-custom-camel-command-processing-core' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/command/core/dependencies.gradle b/custom/apachecon/command/core/dependencies.gradle new file mode 100644 index 00000000000..016fd687f67 --- /dev/null +++ b/custom/apachecon/command/core/dependencies.gradle @@ -0,0 +1,24 @@ +/** + * 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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation('org.springframework.boot:spring-boot-starter') + compileOnly('org.eclipse.persistence:org.eclipse.persistence.jpa') +} diff --git a/custom/apachecon/command/core/src/main/java/org/apache/fineract/apachecon/command/core/CamelCommandProperties.java b/custom/apachecon/command/core/src/main/java/org/apache/fineract/apachecon/command/core/CamelCommandProperties.java new file mode 100644 index 00000000000..f587323ca6d --- /dev/null +++ b/custom/apachecon/command/core/src/main/java/org/apache/fineract/apachecon/command/core/CamelCommandProperties.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.command.core; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "fineract.apachecon.command") +public class CamelCommandProperties { + + private Boolean enabled = true; +} diff --git a/custom/apachecon/command/data/build.gradle b/custom/apachecon/command/data/build.gradle new file mode 100644 index 00000000000..84c15f38ea4 --- /dev/null +++ b/custom/apachecon/command/data/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Camel Command Processing Data' + +group = 'org.apache.fineract.apachecon.command' + +archivesBaseName = 'fineract-custom-camel-command-processing-data' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/command/data/dependencies.gradle b/custom/apachecon/command/data/dependencies.gradle new file mode 100644 index 00000000000..eff650fcf62 --- /dev/null +++ b/custom/apachecon/command/data/dependencies.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + 'org.springframework.boot:spring-boot-starter', + ) +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandExecuteRequest.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandExecuteRequest.java new file mode 100644 index 00000000000..39a3033c2df --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandExecuteRequest.java @@ -0,0 +1,51 @@ +/** + * 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.apachecon.command.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.apache.fineract.batch.exception.ErrorInfo; +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; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelCommandExecuteRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private CommandWrapper wrapper; + private JsonCommand command; + private boolean approvedByChecker; + private ErrorInfo errorInfo; + private CommandProcessingResult result; + private CommandSource commandSource; + +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandProcessingResult.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandProcessingResult.java new file mode 100644 index 00000000000..49364420ced --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandProcessingResult.java @@ -0,0 +1,41 @@ +package org.apache.fineract.apachecon.command.data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.apache.fineract.infrastructure.core.domain.ExternalId; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelCommandProcessingResult implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long commandId; + private Long officeId; + private Long groupId; + private Long clientId; + private Long loanId; + private Long savingsId; + private Long resourceId; + private Long subResourceId; + private String transactionId; + private Map changes; + private Map creditBureauReportData; + private String resourceIdentifier; + private Long productId; + private Long gsimId; + private Long glimId; + private Boolean rollbackTransaction; + private ExternalId resourceExternalId; + private ExternalId subResourceExternalId; +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandValidateRequest.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandValidateRequest.java new file mode 100644 index 00000000000..75552d3a538 --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandValidateRequest.java @@ -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. + */ +package org.apache.fineract.apachecon.command.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.useradministration.domain.AppUser; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelCommandValidateRequest { + + private CommandWrapper wrapper; + private AppUser user; + private boolean validated; +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandWrapper.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandWrapper.java new file mode 100644 index 00000000000..24abd8d8c0e --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelCommandWrapper.java @@ -0,0 +1,41 @@ +package org.apache.fineract.apachecon.command.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelCommandWrapper implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long commandId; + private Long officeId; + private Long groupId; + private Long clientId; + private Long loanId; + private Long savingsId; + private String actionName; + private String entityName; + private String taskPermissionName; + private Long entityId; + private Long subentityId; + private String href; + private String json; + private String transactionId; + private Long productId; + private Long creditBureauId; + private Long organisationCreditBureauId; + private String jobName; + private String idempotencyKey; + private Long templateId; +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelErrorInfo.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelErrorInfo.java new file mode 100644 index 00000000000..b30478a72c7 --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelErrorInfo.java @@ -0,0 +1,26 @@ +package org.apache.fineract.apachecon.command.data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelErrorInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Integer statusCode; + private Integer errorCode; + private String message; + private Set headers; +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelErrorInfoHeader.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelErrorInfoHeader.java new file mode 100644 index 00000000000..b031dd12e98 --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelErrorInfoHeader.java @@ -0,0 +1,23 @@ +package org.apache.fineract.apachecon.command.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelErrorInfoHeader implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String name; + private String value; +} diff --git a/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelJsonCommand.java b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelJsonCommand.java new file mode 100644 index 00000000000..b1a840a590f --- /dev/null +++ b/custom/apachecon/command/data/src/main/java/org/apache/fineract/apachecon/command/data/CamelJsonCommand.java @@ -0,0 +1,37 @@ +package org.apache.fineract.apachecon.command.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public final class CamelJsonCommand implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String jsonCommand; + private String parsedCommand; + private Long commandId; + private Long resourceId; + private Long subresourceId; + private Long groupId; + private Long clientId; + private Long loanId; + private Long savingsId; + private String entityName; + private String transactionId; + private String url; + private Long productId; + private Long creditBureauId; + private Long organisationCreditBureauId; + private String jobName; +} diff --git a/custom/apachecon/command/mapping/build.gradle b/custom/apachecon/command/mapping/build.gradle new file mode 100644 index 00000000000..64aa33a5933 --- /dev/null +++ b/custom/apachecon/command/mapping/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Camel Command Processing Mapping' + +group = 'org.apache.fineract.apachecon.command' + +archivesBaseName = 'fineract-custom-camel-command-processing-mapping' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/command/mapping/dependencies.gradle b/custom/apachecon/command/mapping/dependencies.gradle new file mode 100644 index 00000000000..4dec3fb25b8 --- /dev/null +++ b/custom/apachecon/command/mapping/dependencies.gradle @@ -0,0 +1,30 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + project(':custom:apachecon:command:data'), + 'org.springframework.boot:spring-boot-starter', + 'org.mapstruct:mapstruct', + 'com.google.code.gson:gson', + ) + compileOnly(project(':fineract-provider')) + annotationProcessor('org.mapstruct:mapstruct-processor') +} diff --git a/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelCommandProcessingResultMapper.java b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelCommandProcessingResultMapper.java new file mode 100644 index 00000000000..2b4b25ff874 --- /dev/null +++ b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelCommandProcessingResultMapper.java @@ -0,0 +1,25 @@ +package org.apache.fineract.apachecon.command.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import org.apache.fineract.apachecon.command.data.CamelCommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS) +public abstract class CamelCommandProcessingResultMapper { + + public abstract CamelCommandProcessingResult map(CommandProcessingResult source); + + public CommandProcessingResult map(CamelCommandProcessingResult source) { + if (source == null) { + return null; + } + + return CommandProcessingResult.fromDetails(source.getCommandId(), source.getOfficeId(), source.getGroupId(), source.getClientId(), + source.getLoanId(), source.getSavingsId(), source.getResourceIdentifier(), source.getResourceId(), source.getGsimId(), + source.getGlimId(), source.getCreditBureauReportData(), source.getTransactionId(), source.getChanges(), + source.getProductId(), source.getRollbackTransaction(), source.getSubResourceId(), source.getResourceExternalId(), + source.getSubResourceExternalId()); + } +} diff --git a/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelCommandWrapperMapper.java b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelCommandWrapperMapper.java new file mode 100644 index 00000000000..0f0b4d1879b --- /dev/null +++ b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelCommandWrapperMapper.java @@ -0,0 +1,15 @@ +package org.apache.fineract.apachecon.command.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import org.apache.fineract.apachecon.command.data.CamelCommandWrapper; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS) +public interface CamelCommandWrapperMapper { + + CamelCommandWrapper map(CommandWrapper source); + + CommandWrapper map(CamelCommandWrapper source); +} diff --git a/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelErrorInfoHeaderMapper.java b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelErrorInfoHeaderMapper.java new file mode 100644 index 00000000000..b55d0352c13 --- /dev/null +++ b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelErrorInfoHeaderMapper.java @@ -0,0 +1,15 @@ +package org.apache.fineract.apachecon.command.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import org.apache.fineract.apachecon.command.data.CamelErrorInfoHeader; +import org.apache.fineract.batch.domain.Header; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS) +public interface CamelErrorInfoHeaderMapper { + + CamelErrorInfoHeader map(Header source); + + Header map(CamelErrorInfoHeader source); +} diff --git a/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelErrorInfoMapper.java b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelErrorInfoMapper.java new file mode 100644 index 00000000000..10e765de13b --- /dev/null +++ b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelErrorInfoMapper.java @@ -0,0 +1,15 @@ +package org.apache.fineract.apachecon.command.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import org.apache.fineract.apachecon.command.data.CamelErrorInfo; +import org.apache.fineract.batch.exception.ErrorInfo; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS, uses = { CamelErrorInfoHeaderMapper.class }) +public interface CamelErrorInfoMapper { + + CamelErrorInfo map(ErrorInfo source); + + ErrorInfo map(CamelErrorInfo source); +} diff --git a/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelJsonCommandMapper.java b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelJsonCommandMapper.java new file mode 100644 index 00000000000..425d6cce811 --- /dev/null +++ b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/CamelJsonCommandMapper.java @@ -0,0 +1,39 @@ +package org.apache.fineract.apachecon.command.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import org.apache.fineract.apachecon.command.data.CamelJsonCommand; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.beans.factory.annotation.Autowired; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS) +public abstract class CamelJsonCommandMapper { + + @Autowired + protected FromJsonHelper fromJsonHelper; + + @Autowired + protected GsonMapper gsonMapper; + + @Mapping(target = "jsonCommand", expression = "java( source.json() )") + @Mapping(target = "parsedCommand", expression = "java( gsonMapper.map(source.parsedJson()) )") + @Mapping(target = "commandId", expression = "java( source.commandId() )") + @Mapping(target = "resourceId", expression = "java( source.entityId() )") + @Mapping(target = "subresourceId", expression = "java( source.subentityId() )") + @Mapping(target = "entityName", expression = "java( source.entityName() )") + public abstract CamelJsonCommand map(JsonCommand source); + + public JsonCommand map(CamelJsonCommand source) { + if (source == null) { + return null; + } + + return JsonCommand.fromExistingCommand(source.getCommandId(), source.getJsonCommand(), gsonMapper.map(source.getParsedCommand()), + fromJsonHelper, source.getEntityName(), source.getResourceId(), source.getSubresourceId(), source.getGroupId(), + source.getClientId(), source.getLoanId(), source.getSavingsId(), source.getTransactionId(), source.getUrl(), + source.getProductId(), source.getCreditBureauId(), source.getOrganisationCreditBureauId(), source.getJobName()); + } +} diff --git a/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/GsonMapper.java b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/GsonMapper.java new file mode 100644 index 00000000000..f107883066f --- /dev/null +++ b/custom/apachecon/command/mapping/src/main/java/org/apache/fineract/apachecon/command/mapping/GsonMapper.java @@ -0,0 +1,24 @@ +package org.apache.fineract.apachecon.command.mapping; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public final class GsonMapper { + + private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); + + public JsonElement map(String source) { + return gson.toJsonTree(source); + } + + public String map(JsonElement source) { + return gson.toJson(source); + } +} diff --git a/custom/apachecon/command/service/build.gradle b/custom/apachecon/command/service/build.gradle new file mode 100644 index 00000000000..83de056e60e --- /dev/null +++ b/custom/apachecon/command/service/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Camel Command Processing Service' + +group = 'org.apache.fineract.apachecon.command' + +archivesBaseName = 'fineract-custom-camel-command-processing-service' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/command/service/dependencies.gradle b/custom/apachecon/command/service/dependencies.gradle new file mode 100644 index 00000000000..26593532a62 --- /dev/null +++ b/custom/apachecon/command/service/dependencies.gradle @@ -0,0 +1,44 @@ +/** + * 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. + */ + +dependencies { + compileOnly( + 'org.springframework.boot:spring-boot-autoconfigure', + 'org.eclipse.persistence:org.eclipse.persistence.jpa' + ) + implementation( + project(':fineract-core'), + project(':fineract-provider'), + project(':custom:apachecon:command:core'), + project(':custom:apachecon:command:data'), + project(':custom:apachecon:command:mapping'), + 'org.springframework.data:spring-data-commons', + 'org.apache.camel.springboot:camel-spring-boot-starter', + 'org.apache.camel.springboot:camel-spring-starter', + 'org.apache.camel.springboot:camel-spring-security-starter', + 'org.apache.camel.springboot:camel-language-starter', + 'org.apache.camel.springboot:camel-direct-starter', + 'org.apache.camel.springboot:camel-disruptor-starter', + 'org.apache.camel.springboot:camel-jgroups-starter', + 'org.apache.camel.springboot:camel-nats-starter', + 'org.apache.camel.springboot:camel-jackson-starter', + 'org.apache.httpcomponents:httpcore', + 'jakarta.servlet:jakarta.servlet-api', + ) +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandErrorProcessor.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandErrorProcessor.java new file mode 100644 index 00000000000..732e45fce0b --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandErrorProcessor.java @@ -0,0 +1,67 @@ +/** + * 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.apachecon.command.service; + +import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR; +import static org.apache.http.HttpStatus.SC_OK; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.Exchange; +import org.apache.fineract.apachecon.command.data.CamelCommandExecuteRequest; +import org.apache.fineract.apachecon.command.mapping.CamelErrorInfoMapper; +import org.apache.fineract.batch.exception.ErrorInfo; +import org.apache.fineract.commands.domain.CommandSource; +import org.apache.fineract.commands.service.CommandSourceService; +import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; +import org.apache.fineract.infrastructure.core.exception.ErrorHandler; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class CamelCommandErrorProcessor { + + private final CommandSourceService commandSourceService; + private final CamelErrorInfoMapper errorInfoMapper; + + public CamelCommandExecuteRequest onError(Exchange exchange) { + Throwable t = exchange.getException(); + CamelCommandExecuteRequest request = exchange.getMessage(CamelCommandExecuteRequest.class); + CommandSource commandSource = request.getCommandSource(); + + RuntimeException mappable = ErrorHandler.getMappable(t); + ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable); + Integer statusCode = errorInfo.getStatusCode(); + commandSource.setResultStatusCode(statusCode); + commandSource.setResult(errorInfo.getMessage()); + + if (statusCode != SC_OK) { + commandSource.setStatus(ERROR); + } + + if (!BatchRequestContextHolder.isEnclosingTransaction()) { + commandSource = commandSourceService.saveResultNewTransaction(commandSource); + } + + // must not throw any exception; must persist in new transaction as the current transaction was already + // marked as rollback + return request.toBuilder().commandSource(commandSource).errorInfo(errorInfo).build(); + } +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandExecuteProcessor.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandExecuteProcessor.java new file mode 100644 index 00000000000..d2dd4909a8e --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandExecuteProcessor.java @@ -0,0 +1,216 @@ +/** + * 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.apachecon.command.service; + +import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED; +import static org.apache.fineract.commands.service.SynchronousCommandProcessingService.IDEMPOTENCY_KEY_STORE_FLAG; +import static org.apache.http.HttpStatus.SC_OK; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.command.data.CamelCommandExecuteRequest; +import org.apache.fineract.apachecon.command.mapping.CamelCommandProcessingResultMapper; +import org.apache.fineract.apachecon.command.mapping.CamelCommandWrapperMapper; +import org.apache.fineract.apachecon.command.mapping.CamelJsonCommandMapper; +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.UnsupportedCommandException; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.commands.provider.CommandHandlerProvider; +import org.apache.fineract.commands.service.CommandSourceService; +import org.apache.fineract.commands.service.IdempotencyKeyResolver; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; +import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; +import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException; +import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException; +import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class CamelCommandExecuteProcessor { + + public static final String COMMAND_SOURCE_ID = "commandSourceId"; + private final PlatformSecurityContext context; + private final ApplicationContext applicationContext; + private final ConfigurationDomainService configurationDomainService; + private final CommandHandlerProvider commandHandlerProvider; + private final IdempotencyKeyResolver idempotencyKeyResolver; + private final CommandSourceService commandSourceService; + private final FineractRequestContextHolder fineractRequestContextHolder; + private final CamelCommandWrapperMapper commandWrapperMapper; + private final CamelJsonCommandMapper jsonCommandMapper; + private final CamelCommandProcessingResultMapper commandProcessingResultMapper; + private final ObjectMapper mapper = new ObjectMapper(); + + @SneakyThrows + public CamelCommandExecuteRequest execute(CamelCommandExecuteRequest request) { + // Do not store the idempotency key because of the exception handling + fineractRequestContextHolder.setAttribute(IDEMPOTENCY_KEY_STORE_FLAG, false); + + Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null); + boolean isRetry = commandId != null; + boolean isEnclosingTransaction = BatchRequestContextHolder.isEnclosingTransaction(); + CommandSource commandSource = null; + var commandWrapper = request.getWrapper(); + var jsonCommand = request.getCommand(); + String idempotencyKey; + + if (isRetry) { + commandSource = commandSourceService.getCommandSource(commandId); + idempotencyKey = commandSource.getIdempotencyKey(); + } else if ((commandId = request.getCommand().commandId()) != null) { // action on the command itself + commandSource = commandSourceService.getCommandSource(commandId); + idempotencyKey = commandSource.getIdempotencyKey(); + } else { + idempotencyKey = idempotencyKeyResolver.resolve(commandWrapper); + } + + exceptionWhenTheRequestAlreadyProcessed(commandWrapper, idempotencyKey, isRetry); + + AppUser user = context.authenticatedUser(commandWrapper); + if (commandSource == null) { + if (isEnclosingTransaction) { + commandSource = commandSourceService.getInitialCommandSource(commandWrapper, jsonCommand, user, idempotencyKey); + } else { + commandSource = commandSourceService.saveInitialNewTransaction(commandWrapper, jsonCommand, user, idempotencyKey); + commandId = commandSource.getId(); + } + } + if (commandId != null) { + storeCommandIdInContext(commandSource); // Store command id as a request attribute + } + + boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(request.getWrapper().getTaskPermissionName()); + if (request.isApprovedByChecker() || (isMakerChecker && user.isCheckerSuperUser())) { + commandSource.markAsChecked(user); + } + + fineractRequestContextHolder.setAttribute(IDEMPOTENCY_KEY_STORE_FLAG, true); + + final CommandProcessingResult result = commandSourceService.processCommand(findCommandHandler(commandWrapper), jsonCommand, + commandSource, user, request.isApprovedByChecker(), isMakerChecker); + + commandSource.setResultStatusCode(SC_OK); + commandSource.updateForAudit(result); + commandSource.setResult(mapper.writeValueAsString(result)); + commandSource.setStatus(PROCESSED); + commandSource = commandSourceService.saveResultSameTransaction(commandSource); + + storeCommandIdInContext(commandSource); // Store command id as a request attribute + + result.setRollbackTransaction(null); + + return request.toBuilder().result(result).commandSource(commandSource).build(); + } + + private void exceptionWhenTheRequestAlreadyProcessed(CommandWrapper wrapper, String idempotencyKey, boolean retry) { + CommandSource command = commandSourceService.findCommandSource(wrapper, idempotencyKey); + if (command == null) { + return; + } + CommandProcessingResultType status = CommandProcessingResultType.fromInt(command.getStatus()); + switch (status) { + case UNDER_PROCESSING -> throw new IdempotentCommandProcessUnderProcessingException(wrapper, idempotencyKey); + case PROCESSED -> throw new IdempotentCommandProcessSucceedException(wrapper, idempotencyKey, command); + case ERROR -> { + if (!retry) { + throw new IdempotentCommandProcessFailedException(wrapper, idempotencyKey, command); + } + } + default -> { + } + } + } + + private void storeCommandIdInContext(CommandSource savedCommandSource) { + if (savedCommandSource.getId() == null) { + throw new IllegalStateException("Command source not saved"); + } + // Idempotency filters and retry need this + fineractRequestContextHolder.setAttribute(COMMAND_SOURCE_ID, savedCommandSource.getId()); + } + + private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) { + NewCommandSourceHandler handler; + + if (wrapper.isDatatableResource()) { + if (wrapper.isCreateDatatable()) { + handler = applicationContext.getBean("createDatatableCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDeleteDatatable()) { + handler = applicationContext.getBean("deleteDatatableCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdateDatatable()) { + handler = applicationContext.getBean("updateDatatableCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isCreate()) { + handler = applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdateMultiple()) { + handler = applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdateOneToOne()) { + handler = applicationContext.getBean("updateOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDeleteMultiple()) { + handler = applicationContext.getBean("deleteOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDeleteOneToOne()) { + handler = applicationContext.getBean("deleteOneToOneDatatableEntryCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isRegisterDatatable()) { + handler = applicationContext.getBean("registerDatatableCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } + } else if (wrapper.isNoteResource()) { + if (wrapper.isCreate()) { + handler = applicationContext.getBean("createNoteCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdate()) { + handler = applicationContext.getBean("updateNoteCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDelete()) { + handler = applicationContext.getBean("deleteNoteCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } + } else if (wrapper.isSurveyResource()) { + if (wrapper.isRegisterSurvey()) { + handler = applicationContext.getBean("registerSurveyCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isFullFilSurvey()) { + handler = applicationContext.getBean("fullFilSurveyCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } + } else if (wrapper.isLoanDisburseDetailResource()) { + if (wrapper.isUpdateDisbursementDate()) { + handler = applicationContext.getBean("updateLoanDisburseDateCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.addAndDeleteDisbursementDetails()) { + handler = applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } + } else { + handler = commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName()); + } + + return handler; + } +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandHookEventProcessor.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandHookEventProcessor.java new file mode 100644 index 00000000000..786fb2d481c --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandHookEventProcessor.java @@ -0,0 +1,107 @@ +/** + * 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.apachecon.command.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.command.data.CamelCommandExecuteRequest; +import org.apache.fineract.apachecon.command.mapping.CamelJsonCommandMapper; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.hooks.event.HookEvent; +import org.apache.fineract.infrastructure.hooks.event.HookEventSource; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class CamelCommandHookEventProcessor { + + private final PlatformSecurityContext securityContext; + private final CamelJsonCommandMapper jsonCommandMapper; + private final ObjectMapper mapper = new ObjectMapper(); // TODO: if this needs further configuration + + @SneakyThrows + public HookEvent publish(CamelCommandExecuteRequest request) { + final var entityName = request.getWrapper().getEntityName(); + final var actionName = request.getWrapper().getActionName(); + final var command = request.getCommand(); + + final AppUser appUser = securityContext.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null)); + + final HookEventSource hookEventSource = new HookEventSource(entityName, actionName); + + if (command.json() != null) { + Map myMap; + + try { + myMap = mapper.readValue(command.json(), new TypeReference<>() {}); + } catch (Exception e) { + throw new PlatformApiDataValidationException("error.msg.invalid.json", "The provided JSON is invalid.", new ArrayList<>(), + e); + } + + Map reqmap = new HashMap<>(); + reqmap.put("entityName", entityName); + reqmap.put("actionName", actionName); + reqmap.put("createdBy", securityContext.authenticatedUser().getId()); + reqmap.put("createdByName", securityContext.authenticatedUser().getUsername()); + reqmap.put("createdByFullName", securityContext.authenticatedUser().getDisplayName()); + + reqmap.put("request", myMap); + if (request.getResult() != null) { + reqmap.put("officeId", request.getResult().getOfficeId()); + reqmap.put("clientId", request.getResult().getClientId()); + request.getResult().setOfficeId(null); + reqmap.put("response", request.getResult()); + } else if (request.getErrorInfo() != null) { + reqmap.put("status", "Exception"); + + Map errorMap = new HashMap<>(); + + try { + errorMap = mapper.readValue(request.getErrorInfo().getMessage(), new TypeReference<>() {}); + } catch (Exception e) { + errorMap.put("errorMessage", request.getErrorInfo().getMessage()); + } + + errorMap.put("errorCode", request.getErrorInfo().getErrorCode()); + errorMap.put("statusCode", request.getErrorInfo().getStatusCode()); + + reqmap.put("response", errorMap); + } + + reqmap.put("timestamp", Instant.now().toString()); + + return new HookEvent(hookEventSource, mapper.writeValueAsString(reqmap), appUser, ThreadLocalContextUtil.getContext()); + } + + return null; + } +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandProcessingRoute.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandProcessingRoute.java new file mode 100644 index 00000000000..dabdb80e327 --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandProcessingRoute.java @@ -0,0 +1,51 @@ +/** + * 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.apachecon.command.service; + +import static org.apache.fineract.apachecon.command.service.CommandConstants.FINERACT_ROUTE_COMMAND_EXECTUE; +import static org.apache.fineract.apachecon.command.service.CommandConstants.FINERACT_ROUTE_COMMAND_VALIDATE_ROLLBACK; +import static org.apache.fineract.apachecon.command.service.CommandConstants.FINERACT_ROUTE_HOOK_ERROR_EVENT; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class CamelCommandProcessingRoute extends RouteBuilder { + + private final CamelCommandExecuteProcessor commandExecuteProcessor; + private final CamelCommandValidateRollbackProcessor commandValidateRollbackProcessor; + private final CamelCommandHookEventProcessor commandHookEventProcessor; + + @Override + public void configure() throws Exception { + from("direct://" + FINERACT_ROUTE_COMMAND_EXECTUE).log(LoggingLevel.WARN, "COMMAND EXECUTE: ${body}").bean(commandExecuteProcessor); + + from("direct://" + FINERACT_ROUTE_COMMAND_VALIDATE_ROLLBACK).threads(5, 15).onException(Exception.class).handled(true) + .log(LoggingLevel.ERROR, "COMMAND VALIDATE ROLLBACK: ${exception}").end().bean(commandValidateRollbackProcessor); + + from("direct://" + FINERACT_ROUTE_HOOK_ERROR_EVENT).threads(5, 15).onException(Exception.class).handled(true) + .log(LoggingLevel.ERROR, "HOOK ERROR EVENT: ${exception}").end().bean(commandHookEventProcessor).filter(body().isNotNull()) + .to("spring-event:" + FINERACT_ROUTE_HOOK_ERROR_EVENT); + } +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandProcessingService.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandProcessingService.java new file mode 100644 index 00000000000..dd6e0d0a575 --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandProcessingService.java @@ -0,0 +1,66 @@ +/** + * 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.apachecon.command.service; + +import static org.apache.fineract.apachecon.command.service.CommandConstants.FINERACT_ROUTE_COMMAND_EXECTUE; +import static org.apache.fineract.apachecon.command.service.CommandConstants.FINERACT_ROUTE_COMMAND_VALIDATE_ROLLBACK; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.ExchangePattern; +import org.apache.camel.ProducerTemplate; +import org.apache.fineract.apachecon.command.data.CamelCommandExecuteRequest; +import org.apache.fineract.apachecon.command.data.CamelCommandValidateRequest; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandProcessingService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CamelCommandProcessingService implements CommandProcessingService { + + private final ProducerTemplate producerTemplate; + + @PostConstruct + public void init() { + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: Camel Command Processing Service!!!"); + } + + @Override + public CommandProcessingResult executeCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker) { + var response = (CamelCommandExecuteRequest) producerTemplate.sendBody("direct://" + FINERACT_ROUTE_COMMAND_EXECTUE, + ExchangePattern.InOut, + CamelCommandExecuteRequest.builder().wrapper(wrapper).command(command).approvedByChecker(isApprovedByChecker).build()); + + return response.getResult(); + } + + @Override + public boolean validateRollbackCommand(CommandWrapper wrapper, AppUser user) { + var response = (CamelCommandValidateRequest) producerTemplate.sendBody("direct://" + FINERACT_ROUTE_COMMAND_VALIDATE_ROLLBACK, + ExchangePattern.InOut, CamelCommandValidateRequest.builder().wrapper(wrapper).user(user).build()); + + return response.isValidated(); + } +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandValidateRollbackProcessor.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandValidateRollbackProcessor.java new file mode 100644 index 00000000000..2d09d3e7a1d --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CamelCommandValidateRollbackProcessor.java @@ -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. + */ +package org.apache.fineract.apachecon.command.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.command.data.CamelCommandValidateRequest; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class CamelCommandValidateRollbackProcessor { + + private final ConfigurationDomainService configurationDomainService; + + public CamelCommandValidateRequest validate(CamelCommandValidateRequest request) { + request.getUser().validateHasPermissionTo(request.getWrapper().getTaskPermissionName()); + boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(request.getWrapper().getTaskPermissionName()); + return request.toBuilder().validated(isMakerChecker && !request.getUser().isCheckerSuperUser()).build(); + } +} diff --git a/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CommandConstants.java b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CommandConstants.java new file mode 100644 index 00000000000..1c9c1e0c2f7 --- /dev/null +++ b/custom/apachecon/command/service/src/main/java/org/apache/fineract/apachecon/command/service/CommandConstants.java @@ -0,0 +1,28 @@ +/** + * 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.apachecon.command.service; + +public final class CommandConstants { + + private CommandConstants() {} + + public static final String FINERACT_ROUTE_COMMAND_EXECTUE = "fineract-command-execute"; + public static final String FINERACT_ROUTE_COMMAND_VALIDATE_ROLLBACK = "fineract-command-validate-rollback"; + public static final String FINERACT_ROUTE_HOOK_ERROR_EVENT = "fineract-hook-error-event"; +} diff --git a/custom/apachecon/command/starter/build.gradle b/custom/apachecon/command/starter/build.gradle new file mode 100644 index 00000000000..0ccedafdc19 --- /dev/null +++ b/custom/apachecon/command/starter/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Camel Command Processing Starter' + +group = 'org.apache.fineract.apachecon.command' + +archivesBaseName = 'fineract-custom-camel-command-processing-starter' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/command/starter/dependencies.gradle b/custom/apachecon/command/starter/dependencies.gradle new file mode 100644 index 00000000000..3ed2f36b004 --- /dev/null +++ b/custom/apachecon/command/starter/dependencies.gradle @@ -0,0 +1,28 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':custom:apachecon:command:core'), + project(':custom:apachecon:command:data'), + project(':custom:apachecon:command:mapping'), + project(':custom:apachecon:command:service'), + 'org.springframework.boot:spring-boot-starter', + ) +} diff --git a/custom/apachecon/command/starter/src/main/java/org/apache/fineract/apachecon/command/starter/CamelCommandAutoConfiguration.java b/custom/apachecon/command/starter/src/main/java/org/apache/fineract/apachecon/command/starter/CamelCommandAutoConfiguration.java new file mode 100644 index 00000000000..30ed800429e --- /dev/null +++ b/custom/apachecon/command/starter/src/main/java/org/apache/fineract/apachecon/command/starter/CamelCommandAutoConfiguration.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.command.starter; + +import org.apache.fineract.apachecon.command.core.CamelCommandProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; + +@AutoConfiguration +@EnableConfigurationProperties({ CamelCommandProperties.class }) +@ComponentScan("org.apache.fineract.apachecon.command") +@ConditionalOnProperty(value = "fineract.apachecon.command.enabled", havingValue = "true") +public class CamelCommandAutoConfiguration {} diff --git a/custom/apachecon/command/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/apachecon/command/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..8f061f0e97e --- /dev/null +++ b/custom/apachecon/command/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.apache.fineract.apachecon.command.starter.CamelCommandAutoConfiguration \ No newline at end of file diff --git a/custom/apachecon/common/configuration/apachecon.env b/custom/apachecon/common/configuration/apachecon.env new file mode 100644 index 00000000000..1f433aed731 --- /dev/null +++ b/custom/apachecon/common/configuration/apachecon.env @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +SPRING_PROFILES_ACTIVE=apachecon,test,diagnostics diff --git a/custom/apachecon/common/configuration/build.gradle b/custom/apachecon/common/configuration/build.gradle new file mode 100644 index 00000000000..ab58fa5fd63 --- /dev/null +++ b/custom/apachecon/common/configuration/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Common Configuration' + +group = 'org.apache.fineract.apachecon.common' + +archivesBaseName = 'fineract-custom-common-configuration' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/common/configuration/dependencies.gradle b/custom/apachecon/common/configuration/dependencies.gradle new file mode 100644 index 00000000000..87c9ace645f --- /dev/null +++ b/custom/apachecon/common/configuration/dependencies.gradle @@ -0,0 +1,30 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + project(':fineract-provider'), + 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter-security', + 'org.springframework.boot:spring-boot-starter-cache', + 'org.springframework.boot:spring-boot-starter-data-redis', + ) + compileOnly('org.springframework.boot:spring-boot-autoconfigure') +} diff --git a/custom/apachecon/common/configuration/src/main/java/org/apache/fineract/apachecon/common/configuration/CommonCacheConfiguration.java b/custom/apachecon/common/configuration/src/main/java/org/apache/fineract/apachecon/common/configuration/CommonCacheConfiguration.java new file mode 100644 index 00000000000..124c3eb8ac6 --- /dev/null +++ b/custom/apachecon/common/configuration/src/main/java/org/apache/fineract/apachecon/common/configuration/CommonCacheConfiguration.java @@ -0,0 +1,54 @@ +/** + * 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.apachecon.common.configuration; + +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService; +import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; + +@Slf4j +@EnableCaching +@Configuration +public class CommonCacheConfiguration { + + private TenantDetailsService tenantDetailsService; + + @Bean + public RedisCacheConfiguration cacheConfiguration() { + return RedisCacheConfiguration.defaultCacheConfig() + .computePrefixWith( + cacheName -> ThreadLocalContextUtil.getTenant() != null ? ThreadLocalContextUtil.getTenant().getName() : "") + .entryTtl(Duration.ofMinutes(60)).disableCachingNullValues() + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); + } + + @Bean + public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() { + return (builder) -> builder.withCacheConfiguration("userCache", + RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10))); + } +} diff --git a/custom/apachecon/common/configuration/src/main/java/org/apache/fineract/apachecon/common/configuration/CommonWebConfiguration.java b/custom/apachecon/common/configuration/src/main/java/org/apache/fineract/apachecon/common/configuration/CommonWebConfiguration.java new file mode 100644 index 00000000000..060634345a7 --- /dev/null +++ b/custom/apachecon/common/configuration/src/main/java/org/apache/fineract/apachecon/common/configuration/CommonWebConfiguration.java @@ -0,0 +1,145 @@ +/** + * 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.apachecon.common.configuration; + +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; +import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; +import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter; +import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter; +import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreHelper; +import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter; +import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.MDCWrapper; +import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter; +import org.apache.fineract.infrastructure.security.data.PlatformRequestLog; +import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter; +import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; +import org.apache.fineract.notification.service.UserNotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.context.SecurityContextHolderFilter; + +@Slf4j +@Configuration +public class CommonWebConfiguration { + + @Autowired + private FineractProperties fineractProperties; + + @Autowired + private ServerProperties serverProperties; + + @Autowired + private BasicAuthenticationEntryPoint basicAuthenticationEntryPoint; + + @Autowired + private AuthenticationManager authenticationManagerBean; + + @Autowired + private ToApiJsonSerializer toApiJsonSerializer; + + @Autowired + private ConfigurationDomainService configurationDomainService; + + @Autowired + private CacheWritePlatformService cacheWritePlatformService; + + @Autowired + private UserNotificationService userNotificationService; + + @Autowired + private BasicAuthTenantDetailsService basicAuthTenantDetailsService; + + @Autowired + private BusinessDateReadPlatformService businessDateReadPlatformService; + + @Autowired + private MDCWrapper mdcWrapper; + + @Autowired + private FineractRequestContextHolder fineractRequestContextHolder; + + @Autowired + private IdempotencyStoreHelper idempotencyStoreHelper; + + @PostConstruct + public void init() { + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: Note web configuration..."); + } + + @Bean + public SecurityFilterChain cocFilterChain(HttpSecurity http) throws Exception { + http.securityMatcher(antMatcher("/coc/**")).authorizeHttpRequests((auth) -> { + auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/coc/**")).permitAll().requestMatchers(antMatcher("/coc/**")) + .fullyAuthenticated(); + }).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint)) + .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) + .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) + .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) + .addFilterAfter(fineractInstanceModeApiFilter(), CorrelationHeaderFilter.class) + .addFilterAfter(idempotencyStoreFilter(), FineractInstanceModeApiFilter.class); + + if (serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/coc/**")).requiresSecure()); + } + + return http.build(); + } + + public RequestResponseFilter requestResponseFilter() { + return new RequestResponseFilter(); + } + + public FineractInstanceModeApiFilter fineractInstanceModeApiFilter() { + return new FineractInstanceModeApiFilter(fineractProperties); + } + + public IdempotencyStoreFilter idempotencyStoreFilter() { + return new IdempotencyStoreFilter(fineractRequestContextHolder, idempotencyStoreHelper, fineractProperties); + } + + public CorrelationHeaderFilter correlationHeaderFilter() { + return new CorrelationHeaderFilter(fineractProperties, mdcWrapper); + } + + public TenantAwareBasicAuthenticationFilter tenantAwareBasicAuthenticationFilter() { + TenantAwareBasicAuthenticationFilter filter = new TenantAwareBasicAuthenticationFilter(authenticationManagerBean, + basicAuthenticationEntryPoint, toApiJsonSerializer, configurationDomainService, cacheWritePlatformService, + userNotificationService, basicAuthTenantDetailsService, businessDateReadPlatformService); + filter.setRequestMatcher(antMatcher("/coc/**")); + return filter; + } +} diff --git a/custom/apachecon/common/configuration/src/main/resources/application-apachecon.properties b/custom/apachecon/common/configuration/src/main/resources/application-apachecon.properties new file mode 100644 index 00000000000..93df8985460 --- /dev/null +++ b/custom/apachecon/common/configuration/src/main/resources/application-apachecon.properties @@ -0,0 +1,35 @@ +# +# 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. +# + +debug=true +# backwards compatibility for 'org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter' +baseUrl=https://localhost:8443${FINERACT_SERVER_SERVLET_CONTEXT_PATH:/fineract-provider} +# camel +fineract.apachecon.command.enabled=true +# note +fineract.apachecon.note.enabled=true +# report +fineract.apachecon.report.enabled=true +fineract.apachecon.report.virtualizer-type=FILE +# redis cache +spring.cache.type=redis +spring.cache.redis.cache-null-values=true +#spring.cache.redis.time-to-live=40000 +spring.redis.host=redis +spring.redis.port=6379 diff --git a/custom/apachecon/note/api/build.gradle b/custom/apachecon/note/api/build.gradle new file mode 100644 index 00000000000..f6b4e0a1dbb --- /dev/null +++ b/custom/apachecon/note/api/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Api' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-api' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/note/api/dependencies.gradle b/custom/apachecon/note/api/dependencies.gradle new file mode 100644 index 00000000000..598d50dd3eb --- /dev/null +++ b/custom/apachecon/note/api/dependencies.gradle @@ -0,0 +1,31 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + project(':fineract-provider'), + project(':custom:apachecon:note:data'), + project(':custom:apachecon:note:domain'), + project(':custom:apachecon:note:service'), + 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter', + 'io.swagger.core.v3:swagger-annotations-jakarta', + ) +} diff --git a/custom/apachecon/note/api/src/main/java/org/apache/fineract/apachecon/note/api/NotesApiController.java b/custom/apachecon/note/api/src/main/java/org/apache/fineract/apachecon/note/api/NotesApiController.java new file mode 100644 index 00000000000..84f41bc56e9 --- /dev/null +++ b/custom/apachecon/note/api/src/main/java/org/apache/fineract/apachecon/note/api/NotesApiController.java @@ -0,0 +1,112 @@ +/** + * 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.apachecon.note.api; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON_VALUE; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.note.data.NoteJdbcData; +import org.apache.fineract.apachecon.note.service.NoteJdbcReadService; +import org.apache.fineract.portfolio.note.api.NotesApiResourceSwagger; +import org.apache.fineract.portfolio.note.domain.NoteType; +import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@Component +@RestController +@RequestMapping(value = "/coc/{resourceType}/{resourceId}/notes", consumes = { APPLICATION_JSON_VALUE }, produces = { + APPLICATION_JSON_VALUE, APPLICATION_PROBLEM_JSON_VALUE }) +@Tag(name = "Notes", description = "Notes API allows to enter notes for supported resources.") +@RequiredArgsConstructor +public class NotesApiController { + // NOTE: only read requests here + + private final NoteJdbcReadService noteReadService; + + @GetMapping + @Operation(summary = "Retrieve a Resource's description", description = "Retrieves a Resource's Notes\n\n" + + "Note: Notes are returned in descending createOn order.\n" + "\n" + "Example Requests:\n" + "\n" + "clients/2/notes\n" + "\n" + + "\n" + "groups/2/notes?fields=note,createdOn,createdByUsername") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = NotesApiResourceSwagger.GetResourceTypeResourceIdNotesResponse.class)))) }) + public Collection retrieveNotesByResource(@PathVariable("resourceType") final String resourceType, + @PathVariable("resourceId") final Long resourceId) { + + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: Note Service - 'retrieveNotesByResource'"); + + final NoteType noteType = NoteType.fromApiUrl(resourceType); + + if (noteType == null) { + throw new NoteResourceNotSupportedException(resourceType); + } + + // TODO: define this in security configuration + // this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType, + // resourceId).entityName()); + + final Integer noteTypeId = noteType.getValue(); + + return this.noteReadService.retrieveNotesByResource(resourceId, noteTypeId); + } + + @GetMapping("/{noteId}") + @Operation(summary = "Retrieve a Resource Note", description = "Retrieves a Resource Note\n\n" + "Example Requests:\n" + "\n" + + "clients/1/notes/76\n" + "\n" + "\n" + "groups/1/notes/20\n" + "\n" + "\n" + + "clients/1/notes/76?fields=note,createdOn,createdByUsername\n" + "\n" + "\n" + + "groups/1/notes/20?fields=note,createdOn,createdByUsername") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = NotesApiResourceSwagger.GetResourceTypeResourceIdNotesNoteIdResponse.class))) }) + public NoteJdbcData retrieveNote(@PathVariable("resourceType") final String resourceType, + @PathVariable("resourceId") final Long resourceId, @PathVariable("noteId") final Long noteId, + @RequestParam(value = "prettyPrint", defaultValue = "true") Boolean prettyPrint, + @RequestParam(value = "template", defaultValue = "false") Boolean template, + @RequestParam(value = "makerCheckerable", defaultValue = "false") Boolean makerCheckerable, + @RequestParam(value = "includeJson", defaultValue = "true") Boolean includeJson) { + + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: Note Service - 'retrieveNote'"); + + final NoteType noteType = NoteType.fromApiUrl(resourceType); + + if (noteType == null) { + throw new NoteResourceNotSupportedException(resourceType); + } + + // TODO: define this in security configuration + // this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType, + // resourceId).entityName()); + + return this.noteReadService.retrieveNote(noteId, resourceId, noteType.getValue()); + } +} diff --git a/custom/apachecon/note/api/src/main/java/org/apache/fineract/apachecon/note/api/NotesPingController.java b/custom/apachecon/note/api/src/main/java/org/apache/fineract/apachecon/note/api/NotesPingController.java new file mode 100644 index 00000000000..7cd49045262 --- /dev/null +++ b/custom/apachecon/note/api/src/main/java/org/apache/fineract/apachecon/note/api/NotesPingController.java @@ -0,0 +1,52 @@ +/** + * 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.apachecon.note.api; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@Component +@RestController +@RequestMapping(value = "/coc/notes/ping", consumes = { APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE }, produces = { APPLICATION_JSON_VALUE, + TEXT_PLAIN_VALUE, APPLICATION_PROBLEM_JSON_VALUE }) +@Tag(name = "Notes", description = "Notes API allows to enter notes for supported resources.") +@RequiredArgsConstructor +public class NotesPingController { + + @GetMapping + public String ping(@RequestParam("message") String message) { + // NOTE: just for quick verification that Spring Web is in place + + final var tenant = ThreadLocalContextUtil.getTenant(); + + return String.format("PONG: %s from %s", message, tenant.getName()); + } +} diff --git a/custom/apachecon/note/configuration/build.gradle b/custom/apachecon/note/configuration/build.gradle new file mode 100644 index 00000000000..e877d512e63 --- /dev/null +++ b/custom/apachecon/note/configuration/build.gradle @@ -0,0 +1,27 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Configuration' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-configuration' + +apply from: 'dependencies.gradle' + +compileJava.dependsOn ':custom:apachecon:note:domain:processResources' diff --git a/custom/apachecon/note/configuration/dependencies.gradle b/custom/apachecon/note/configuration/dependencies.gradle new file mode 100644 index 00000000000..43699178a0d --- /dev/null +++ b/custom/apachecon/note/configuration/dependencies.gradle @@ -0,0 +1,29 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + project(':fineract-provider'), + project(':custom:apachecon:note:domain'), + 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter-security', + ) + compileOnly('org.springframework.boot:spring-boot-autoconfigure') +} diff --git a/custom/apachecon/note/configuration/src/main/java/org/apache/fineract/apachecon/note/configuration/NoteJdbcConfiguration.java b/custom/apachecon/note/configuration/src/main/java/org/apache/fineract/apachecon/note/configuration/NoteJdbcConfiguration.java new file mode 100644 index 00000000000..ee5578af050 --- /dev/null +++ b/custom/apachecon/note/configuration/src/main/java/org/apache/fineract/apachecon/note/configuration/NoteJdbcConfiguration.java @@ -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. + */ +package org.apache.fineract.apachecon.note.configuration; + +import com.infobip.spring.data.jdbc.QuerydslJdbcRepositoryFactoryBean; +import com.querydsl.sql.SQLTemplates; +import com.querydsl.sql.spring.SpringExceptionTranslator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; + +@Configuration +@EnableJdbcRepositories(basePackages = "org.apache.fineract.apachecon.note.domain", jdbcOperationsRef = "namedParameterJdbcTemplate", repositoryFactoryBeanClass = QuerydslJdbcRepositoryFactoryBean.class) +public class NoteJdbcConfiguration { + + @Bean + public com.querydsl.sql.Configuration querydslSqlConfiguration(SQLTemplates sqlTemplates) { + var configuration = new com.querydsl.sql.Configuration(sqlTemplates); + configuration.setExceptionTranslator(new SpringExceptionTranslator()); + // configuration.register(new EnumByNameType<>(Types.VARCHAR, SomeEnum.class)); + return configuration; + } +} diff --git a/custom/apachecon/note/core/build.gradle b/custom/apachecon/note/core/build.gradle new file mode 100644 index 00000000000..b359846b62e --- /dev/null +++ b/custom/apachecon/note/core/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Core' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-core' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/note/core/dependencies.gradle b/custom/apachecon/note/core/dependencies.gradle new file mode 100644 index 00000000000..9e5223a61cb --- /dev/null +++ b/custom/apachecon/note/core/dependencies.gradle @@ -0,0 +1,22 @@ +/** + * 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. + */ + +dependencies { + implementation('org.springframework.boot:spring-boot-starter') +} diff --git a/custom/apachecon/note/core/src/main/java/org/apache/fineract/apachecon/note/core/NoteRelationalProperties.java b/custom/apachecon/note/core/src/main/java/org/apache/fineract/apachecon/note/core/NoteRelationalProperties.java new file mode 100644 index 00000000000..9f954647e65 --- /dev/null +++ b/custom/apachecon/note/core/src/main/java/org/apache/fineract/apachecon/note/core/NoteRelationalProperties.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.note.core; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "fineract.apachecon.note") +public class NoteRelationalProperties { + + private Boolean enabled = true; +} diff --git a/custom/apachecon/note/data/build.gradle b/custom/apachecon/note/data/build.gradle new file mode 100644 index 00000000000..24f227b1dd2 --- /dev/null +++ b/custom/apachecon/note/data/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Data' + +group = 'org.apache.fineract.apachecon.infrastructure.data' + +archivesBaseName = 'fineract-custom-note-data' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/note/data/dependencies.gradle b/custom/apachecon/note/data/dependencies.gradle new file mode 100644 index 00000000000..42f1fe8c48e --- /dev/null +++ b/custom/apachecon/note/data/dependencies.gradle @@ -0,0 +1,23 @@ +/** + * 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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation('org.springframework.boot:spring-boot-starter') +} diff --git a/custom/apachecon/note/data/src/main/java/org/apache/fineract/apachecon/note/data/AppUserJdbcData.java b/custom/apachecon/note/data/src/main/java/org/apache/fineract/apachecon/note/data/AppUserJdbcData.java new file mode 100644 index 00000000000..f362e39463a --- /dev/null +++ b/custom/apachecon/note/data/src/main/java/org/apache/fineract/apachecon/note/data/AppUserJdbcData.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.apachecon.note.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public class AppUserJdbcData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + private String username; + private Long officeId; + private String officeName; + private String firstname; + private String lastname; + private String email; +} diff --git a/custom/apachecon/note/data/src/main/java/org/apache/fineract/apachecon/note/data/NoteJdbcData.java b/custom/apachecon/note/data/src/main/java/org/apache/fineract/apachecon/note/data/NoteJdbcData.java new file mode 100644 index 00000000000..d26b4edbfe2 --- /dev/null +++ b/custom/apachecon/note/data/src/main/java/org/apache/fineract/apachecon/note/data/NoteJdbcData.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.apachecon.note.data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.OffsetDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public class NoteJdbcData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + private Long clientId; + private Long groupId; + private Long loanId; + private Long loanTransactionId; + private Long depositAccountId; + private Long savingAccountId; + private EnumOptionData noteType; + private String note; + private Long createdById; + private String createdByUsername; + private OffsetDateTime createdOn; + private Long updatedById; + private String updatedByUsername; + private OffsetDateTime updatedOn; +} diff --git a/custom/apachecon/note/domain/build.gradle b/custom/apachecon/note/domain/build.gradle new file mode 100644 index 00000000000..e158a85637e --- /dev/null +++ b/custom/apachecon/note/domain/build.gradle @@ -0,0 +1,28 @@ +/** + * 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. + */ +plugins { + id("java-library") +} +description = 'Apache Community over Code EU 2024: Note Domain' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-domain' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/note/domain/dependencies.gradle b/custom/apachecon/note/domain/dependencies.gradle new file mode 100644 index 00000000000..c273e17fa10 --- /dev/null +++ b/custom/apachecon/note/domain/dependencies.gradle @@ -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. + */ + +dependencies { + implementation( + 'org.springframework.boot:spring-boot-starter', + 'org.springframework.boot:spring-boot-starter-data-jdbc', + ) + api( + 'com.infobip:infobip-spring-data-jdbc-querydsl-boot-starter:9.0.7', + 'com.infobip:infobip-spring-data-jdbc-querydsl:9.0.7', + 'com.querydsl:querydsl-sql-spring:5.1.0', + 'com.querydsl:querydsl-collections:5.1.0', + ) + annotationProcessor('com.infobip:infobip-spring-data-jdbc-annotation-processor:9.0.7') +} diff --git a/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/AppUserJdbc.java b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/AppUserJdbc.java new file mode 100644 index 00000000000..a798ab0fb02 --- /dev/null +++ b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/AppUserJdbc.java @@ -0,0 +1,98 @@ +/** + * 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.apachecon.note.domain; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +@Table("m_appuser") +public class AppUserJdbc implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @Column("id") + private Long id; + + @Column("email") + private String email; + + @Column("username") + private String username; + + @Column("firstname") + private String firstname; + + @Column("lastname") + private String lastname; + + @Column("password") + private String password; + + @Column("nonexpired") + private boolean accountNonExpired; + + @Column("nonlocked") + private boolean accountNonLocked; + + @Column("nonexpired_credentials") + private boolean credentialsNonExpired; + + @Column("enabled") + private boolean enabled; + + @Column("firsttime_login_remaining") + private boolean firstTimeLoginRemaining; + + @Column("is_deleted") + private boolean deleted; + + @Column("office_id") + private Long officeId; + + @Column("staff_id") + private Long staffId; + + @Column("last_time_password_updated") + private LocalDate lastTimePasswordUpdated; + + @Column("password_never_expires") + private boolean passwordNeverExpires; + + @Column("is_self_service_user") + private boolean isSelfServiceUser; + + @Column("cannot_change_password") + private Boolean cannotChangePassword; +} diff --git a/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/AppUserJdbcRepository.java b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/AppUserJdbcRepository.java new file mode 100644 index 00000000000..3f4cbdf4881 --- /dev/null +++ b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/AppUserJdbcRepository.java @@ -0,0 +1,27 @@ +/** + * 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.apachecon.note.domain; + +import com.infobip.spring.data.jdbc.QuerydslJdbcFragment; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +public interface AppUserJdbcRepository extends ListCrudRepository, QueryByExampleExecutor, + QuerydslPredicateExecutor, QuerydslJdbcFragment {} diff --git a/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteJdbc.java b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteJdbc.java new file mode 100644 index 00000000000..07eec959f07 --- /dev/null +++ b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteJdbc.java @@ -0,0 +1,86 @@ +/** + * 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.apachecon.note.domain; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +@Table(name = "m_note") +public class NoteJdbc implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @Column("id") + private Long id; + + @Column("created_by") + private Long createdBy; + + @Column("created_date") + private LocalDateTime createdDate; + + @Column("last_modified_by") + private Long lastModifiedBy; + + @Column("lastmodified_date") + private LocalDateTime lastModifiedDate; + + @Column("client_id") + private Long clientId; + + @Column("group_id") + private Long groupId; + + @Column("loan_id") + private Long loanId; + + @Column("loan_transaction_id") + private Long loanTransactionId; + + @Column("savings_account_id") + private Long savingsAccountId; + + @Column("savings_account_transaction_id") + private Long savingsTransactionId; + + @Column("share_account_id") + private Long shareAccountId; + + @Column("note") + private String notes; + + @Column("note_type_enum") + private Integer noteTypeId; +} diff --git a/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteJdbcRepository.java b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteJdbcRepository.java new file mode 100644 index 00000000000..1919f97b58b --- /dev/null +++ b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteJdbcRepository.java @@ -0,0 +1,27 @@ +/** + * 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.apachecon.note.domain; + +import com.infobip.spring.data.jdbc.QuerydslJdbcFragment; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +public interface NoteJdbcRepository extends ListCrudRepository, QueryByExampleExecutor, + QuerydslPredicateExecutor, QuerydslJdbcFragment {} diff --git a/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteType.java b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteType.java new file mode 100644 index 00000000000..1c07ba0db32 --- /dev/null +++ b/custom/apachecon/note/domain/src/main/java/org/apache/fineract/apachecon/note/domain/NoteType.java @@ -0,0 +1,108 @@ +/** + * 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.apachecon.note.domain; + +import java.util.HashMap; +import java.util.Map; + +public enum NoteType { + + CLIENT(100, "noteType.client", "clients"), // + LOAN(200, "noteType.loan", "loans"), // + LOAN_TRANSACTION(300, "noteType.loan.transaction", "loanTransactions"), // + SAVING_ACCOUNT(500, "noteType.saving", "savings"), // + GROUP(600, "noteType.group", "groups"), // + SHARE_ACCOUNT(700, "noteType.shares", "accounts/share"), // + SAVINGS_TRANSACTION(800, "noteType.savings.transaction", "savingsTransactions"); + + private final Integer value; + private final String code; + private final String apiUrl; + + NoteType(final Integer value, final String code, final String apiUrl) { + this.value = value; + this.code = code; + this.apiUrl = apiUrl; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } + + public String getApiUrl() { + return this.apiUrl; + } + + private static final Map intToEnumMap = new HashMap<>(); + private static int minValue; + private static int maxValue; + + static { + int i = 0; + for (final NoteType type : NoteType.values()) { + if (i == 0) { + minValue = type.value; + } + intToEnumMap.put(type.value, type); + if (minValue >= type.value) { + minValue = type.value; + } + if (maxValue < type.value) { + maxValue = type.value; + } + i = i + 1; + } + } + + public static NoteType fromInt(final int i) { + final NoteType type = intToEnumMap.get(Integer.valueOf(i)); + return type; + } + + public static int getMinValue() { + return minValue; + } + + public static int getMaxValue() { + return maxValue; + } + + @Override + public String toString() { + return name().toString(); + } + + private static final Map apiUrlToEnumMap = new HashMap<>(); + + static { + for (final NoteType type : NoteType.values()) { + apiUrlToEnumMap.put(type.apiUrl, type); + } + } + + public static NoteType fromApiUrl(final String url) { + final NoteType type = apiUrlToEnumMap.get(url); + return type; + } + +} diff --git a/custom/apachecon/note/mapping/build.gradle b/custom/apachecon/note/mapping/build.gradle new file mode 100644 index 00000000000..145b8425696 --- /dev/null +++ b/custom/apachecon/note/mapping/build.gradle @@ -0,0 +1,27 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Data' + +group = 'org.apache.fineract.apachecon.infrastructure.data' + +archivesBaseName = 'fineract-custom-note-data' + +apply from: 'dependencies.gradle' + +compileJava.dependsOn ':custom:apachecon:note:domain:processResources' diff --git a/custom/apachecon/note/mapping/dependencies.gradle b/custom/apachecon/note/mapping/dependencies.gradle new file mode 100644 index 00000000000..efc5496af7e --- /dev/null +++ b/custom/apachecon/note/mapping/dependencies.gradle @@ -0,0 +1,30 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + project(':custom:apachecon:note:data'), + project(':custom:apachecon:note:domain'), + 'org.springframework.boot:spring-boot-starter', + 'org.mapstruct:mapstruct', + ) + compileOnly(project(':fineract-provider')) + annotationProcessor('org.mapstruct:mapstruct-processor') +} diff --git a/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/AppUserJdbcMapper.java b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/AppUserJdbcMapper.java new file mode 100644 index 00000000000..3de8cb02b8c --- /dev/null +++ b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/AppUserJdbcMapper.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.note.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import org.apache.fineract.apachecon.note.data.AppUserJdbcData; +import org.apache.fineract.apachecon.note.domain.AppUserJdbc; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS, uses = { DateMapper.class }) +public interface AppUserJdbcMapper { + + AppUserJdbcData map(AppUserJdbc source); +} diff --git a/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/DateMapper.java b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/DateMapper.java new file mode 100644 index 00000000000..de7fc8bbfdd --- /dev/null +++ b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/DateMapper.java @@ -0,0 +1,34 @@ +/** + * 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.apachecon.note.mapping; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class DateMapper { + + public OffsetDateTime map(LocalDateTime source) { + return source.atOffset(ZoneOffset.UTC); + } +} diff --git a/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteEnumerationMapper.java b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteEnumerationMapper.java new file mode 100644 index 00000000000..1d13a5e22b2 --- /dev/null +++ b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteEnumerationMapper.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.note.mapping; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.portfolio.note.service.NoteEnumerations; +import org.springframework.stereotype.Component; + +@Component +public class NoteEnumerationMapper { + + public EnumOptionData map(Integer source) { + return NoteEnumerations.noteType(source); + } +} diff --git a/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteFacadeMapper.java b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteFacadeMapper.java new file mode 100644 index 00000000000..af8dd2f1896 --- /dev/null +++ b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteFacadeMapper.java @@ -0,0 +1,35 @@ +/** + * 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.apachecon.note.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import java.util.Collection; +import java.util.List; +import org.apache.fineract.apachecon.note.data.NoteJdbcData; +import org.apache.fineract.portfolio.note.data.NoteData; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS, uses = { NoteEnumerationMapper.class, DateMapper.class }) +public interface NoteFacadeMapper { + + NoteData map(NoteJdbcData source); + + List mapAll(Collection source); +} diff --git a/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteJdbcMapper.java b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteJdbcMapper.java new file mode 100644 index 00000000000..7072a24b499 --- /dev/null +++ b/custom/apachecon/note/mapping/src/main/java/org/apache/fineract/apachecon/note/mapping/NoteJdbcMapper.java @@ -0,0 +1,42 @@ +/** + * 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.apachecon.note.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import java.util.List; +import org.apache.fineract.apachecon.note.data.NoteJdbcData; +import org.apache.fineract.apachecon.note.domain.NoteJdbc; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = ALWAYS, uses = { NoteEnumerationMapper.class, DateMapper.class }) +public interface NoteJdbcMapper { + + @Mapping(source = "createdBy", target = "createdById") + @Mapping(source = "lastModifiedBy", target = "updatedById") + @Mapping(source = "notes", target = "note") + @Mapping(source = "createdDate", target = "createdOn") + @Mapping(source = "lastModifiedDate", target = "updatedOn") + @Mapping(source = "savingsAccountId", target = "savingAccountId") + // @Mapping(source = "savingsTransactionId", target = "") + NoteJdbcData map(NoteJdbc source); + + List mapAll(List sources); +} diff --git a/custom/apachecon/note/migration/build.gradle b/custom/apachecon/note/migration/build.gradle new file mode 100644 index 00000000000..1e70b46b6e2 --- /dev/null +++ b/custom/apachecon/note/migration/build.gradle @@ -0,0 +1,23 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Migration' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-migration' diff --git a/custom/apachecon/note/migration/src/main/resource/resources/db/custom-changelog/0001_apachecon_note_sample.xml b/custom/apachecon/note/migration/src/main/resource/resources/db/custom-changelog/0001_apachecon_note_sample.xml new file mode 100644 index 00000000000..fd4143d34df --- /dev/null +++ b/custom/apachecon/note/migration/src/main/resource/resources/db/custom-changelog/0001_apachecon_note_sample.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/custom/apachecon/note/service/build.gradle b/custom/apachecon/note/service/build.gradle new file mode 100644 index 00000000000..76eab6c60b8 --- /dev/null +++ b/custom/apachecon/note/service/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Service' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-service' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/note/service/dependencies.gradle b/custom/apachecon/note/service/dependencies.gradle new file mode 100644 index 00000000000..32ef24b0524 --- /dev/null +++ b/custom/apachecon/note/service/dependencies.gradle @@ -0,0 +1,28 @@ +/** + * 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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation(project(':fineract-provider')) + implementation(project(':custom:apachecon:note:core')) + implementation(project(':custom:apachecon:note:data')) + implementation(project(':custom:apachecon:note:domain')) + implementation(project(':custom:apachecon:note:mapping')) + compileOnly('org.springframework.boot:spring-boot-autoconfigure') +} diff --git a/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/AppUserJdbcReadService.java b/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/AppUserJdbcReadService.java new file mode 100644 index 00000000000..4e4efc02f8e --- /dev/null +++ b/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/AppUserJdbcReadService.java @@ -0,0 +1,50 @@ +/** + * 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.apachecon.note.service; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.note.data.AppUserJdbcData; +import org.apache.fineract.apachecon.note.domain.AppUserJdbcRepository; +import org.apache.fineract.apachecon.note.mapping.AppUserJdbcMapper; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +@CacheConfig(cacheNames = "userCache") +public class AppUserJdbcReadService /* implements AppUserReadPlatformService */ { + + private final AppUserJdbcRepository appUserJdbcRepository; + + private final AppUserJdbcMapper appUserJdbcMapper; + + @PostConstruct + public void init() { + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: App User Service!!!"); + } + + @Cacheable + public AppUserJdbcData retrieveUser(Long id) { + return appUserJdbcMapper.map(appUserJdbcRepository.findById(id).orElse(null)); + } +} diff --git a/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/NoteJdbcReadService.java b/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/NoteJdbcReadService.java new file mode 100644 index 00000000000..6e92205812a --- /dev/null +++ b/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/NoteJdbcReadService.java @@ -0,0 +1,121 @@ +/** + * 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.apachecon.note.service; + +import com.querydsl.core.types.dsl.BooleanExpression; +import jakarta.annotation.PostConstruct; +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.note.data.NoteJdbcData; +import org.apache.fineract.apachecon.note.domain.NoteJdbcRepository; +import org.apache.fineract.apachecon.note.domain.NoteType; +import org.apache.fineract.apachecon.note.domain.QNoteJdbc; +import org.apache.fineract.apachecon.note.mapping.NoteJdbcMapper; +import org.apache.fineract.portfolio.note.exception.NoteNotFoundException; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class NoteJdbcReadService /* implements NoteReadPlatformService */ { + + private final NoteJdbcRepository noteJdbcRepository; + + private final NoteJdbcMapper noteJdbcMapper; + + private final AppUserJdbcReadService appUserJdbcReadService; + + @PostConstruct + public void init() { + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: Note Service!!!"); + } + + // NOTE: we know this is used only once, so let's create something similar instead of being tied to the original + // implementation + // @Override + public NoteJdbcData retrieveNote(Long noteId, Long resourceId, Integer noteTypeId) { + final NoteType noteType = NoteType.fromInt(noteTypeId); + + var note$ = QNoteJdbc.noteJdbc; + + var criteria = addResourceCriteria(note$.id.eq(noteId), noteType, resourceId); + + log.warn("Note query: {}", criteria); + + var result = noteJdbcRepository.queryOne( + query -> query.select(noteJdbcRepository.entityProjection()).from(note$).where(criteria).orderBy(note$.createdBy.desc())); + + if (result.isPresent()) { + var noteData = noteJdbcMapper.map(result.get()); + + var createdBy = appUserJdbcReadService.retrieveUser(noteData.getCreatedById()); + var modifiedBy = appUserJdbcReadService.retrieveUser(noteData.getUpdatedById()); + + if (createdBy != null) { + noteData.setCreatedByUsername(createdBy.getUsername()); + } + if (modifiedBy != null) { + noteData.setCreatedByUsername(modifiedBy.getUsername()); + } + + return noteData; + } + + throw new NoteNotFoundException(noteId, resourceId, noteType.name().toLowerCase()); + } + + // @Override + public Collection retrieveNotesByResource(Long resourceId, Integer noteTypeId) { + final var noteType = NoteType.fromInt(noteTypeId); + + var note$ = QNoteJdbc.noteJdbc; + + var criteria = addResourceCriteria(note$.id.gt(0), noteType, resourceId); + + log.warn("Note query: {}", criteria); + + return noteJdbcMapper.mapAll(noteJdbcRepository.queryMany( + query -> query.select(noteJdbcRepository.entityProjection()).from(note$).where(criteria).orderBy(note$.createdBy.desc()))); + } + + public BooleanExpression addResourceCriteria(BooleanExpression criteria, NoteType noteType, Long resourceId) { + var note$ = QNoteJdbc.noteJdbc; + + switch (noteType) { + case CLIENT: + log.warn("... filter client data... {} - {}", resourceId, noteType.getValue()); + return criteria.and(note$.clientId.eq(resourceId)).and(note$.noteTypeId.eq(NoteType.CLIENT.getValue())); + case LOAN: + return criteria.and(note$.loanId.eq(resourceId)) + .and(note$.noteTypeId.eq(NoteType.LOAN.getValue()).or(note$.noteTypeId.eq(NoteType.LOAN_TRANSACTION.getValue()))); + case LOAN_TRANSACTION: + return criteria.and(note$.loanTransactionId.eq(resourceId)); + case SAVING_ACCOUNT: + return criteria.and(note$.savingsAccountId.eq(resourceId)).and(note$.noteTypeId.eq(NoteType.SAVING_ACCOUNT.getValue()) + .or(note$.noteTypeId.eq(NoteType.SAVINGS_TRANSACTION.getValue()))); + case SAVINGS_TRANSACTION: + return criteria.and(note$.savingsTransactionId.eq(resourceId)); + case GROUP: + return criteria.and(note$.groupId.eq(resourceId)); + default: + return criteria; + } + } +} diff --git a/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/NoteReadPlaformFacadeService.java b/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/NoteReadPlaformFacadeService.java new file mode 100644 index 00000000000..ee585e1e562 --- /dev/null +++ b/custom/apachecon/note/service/src/main/java/org/apache/fineract/apachecon/note/service/NoteReadPlaformFacadeService.java @@ -0,0 +1,54 @@ +/** + * 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.apachecon.note.service; + +import jakarta.annotation.PostConstruct; +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.apachecon.note.mapping.NoteFacadeMapper; +import org.apache.fineract.portfolio.note.data.NoteData; +import org.apache.fineract.portfolio.note.service.NoteReadPlatformService; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class NoteReadPlaformFacadeService implements NoteReadPlatformService { + + private final NoteJdbcReadService noteJdbcReadService; + + private final NoteFacadeMapper noteFacadeMapper; + + @PostConstruct + public void init() { + log.warn("We have replaced the original {} with an implementation that uses native - type safe - SQL queries!", + NoteReadPlatformService.class.getName()); + } + + @Override + public NoteData retrieveNote(Long noteId, Long resourceId, Integer noteTypeId) { + return noteFacadeMapper.map(noteJdbcReadService.retrieveNote(noteId, resourceId, noteTypeId)); + } + + @Override + public Collection retrieveNotesByResource(Long resourceId, Integer noteTypeId) { + return noteFacadeMapper.mapAll(noteJdbcReadService.retrieveNotesByResource(resourceId, noteTypeId)); + } +} diff --git a/custom/apachecon/note/starter/build.gradle b/custom/apachecon/note/starter/build.gradle new file mode 100644 index 00000000000..8b834a06541 --- /dev/null +++ b/custom/apachecon/note/starter/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Note Starter' + +group = 'org.apache.fineract.apachecon.infrastructure.note' + +archivesBaseName = 'fineract-custom-note-starter' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/note/starter/dependencies.gradle b/custom/apachecon/note/starter/dependencies.gradle new file mode 100644 index 00000000000..5703c64389c --- /dev/null +++ b/custom/apachecon/note/starter/dependencies.gradle @@ -0,0 +1,31 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':custom:apachecon:note:configuration'), + project(':custom:apachecon:note:core'), + project(':custom:apachecon:note:data'), + project(':custom:apachecon:note:domain'), + project(':custom:apachecon:note:migration'), + project(':custom:apachecon:note:service'), + project(':custom:apachecon:note:api'), + ) + compileOnly('org.springframework.boot:spring-boot-autoconfigure') +} diff --git a/custom/apachecon/note/starter/src/main/java/org/apache/fineract/apachecon/note/starter/NoteJdbcAutoConfiguration.java b/custom/apachecon/note/starter/src/main/java/org/apache/fineract/apachecon/note/starter/NoteJdbcAutoConfiguration.java new file mode 100644 index 00000000000..99cce16f6d8 --- /dev/null +++ b/custom/apachecon/note/starter/src/main/java/org/apache/fineract/apachecon/note/starter/NoteJdbcAutoConfiguration.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.note.starter; + +import org.apache.fineract.apachecon.note.core.NoteRelationalProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; + +@AutoConfiguration +@EnableConfigurationProperties({ NoteRelationalProperties.class }) +@ComponentScan("org.apache.fineract.apachecon.note") +@ConditionalOnProperty(value = "fineract.apachecon.note.enabled", havingValue = "true") +public class NoteJdbcAutoConfiguration {} diff --git a/custom/apachecon/note/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/apachecon/note/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..aafb00f7616 --- /dev/null +++ b/custom/apachecon/note/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.apache.fineract.apachecon.note.starter.NoteJdbcAutoConfiguration \ No newline at end of file diff --git a/custom/apachecon/report/core/build.gradle b/custom/apachecon/report/core/build.gradle new file mode 100644 index 00000000000..3dd36f13ec1 --- /dev/null +++ b/custom/apachecon/report/core/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Jasper Reporting Process Core' + +group = 'org.apache.fineract.apachecon.infrastructure.report' + +archivesBaseName = 'fineract-custom-jasper-report-core' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/report/core/dependencies.gradle b/custom/apachecon/report/core/dependencies.gradle new file mode 100644 index 00000000000..42f1fe8c48e --- /dev/null +++ b/custom/apachecon/report/core/dependencies.gradle @@ -0,0 +1,23 @@ +/** + * 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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation('org.springframework.boot:spring-boot-starter') +} diff --git a/custom/apachecon/report/core/src/main/java/org/apache/fineract/apachecon/report/core/JasperReportingProperties.java b/custom/apachecon/report/core/src/main/java/org/apache/fineract/apachecon/report/core/JasperReportingProperties.java new file mode 100644 index 00000000000..678d25db251 --- /dev/null +++ b/custom/apachecon/report/core/src/main/java/org/apache/fineract/apachecon/report/core/JasperReportingProperties.java @@ -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. + */ +package org.apache.fineract.apachecon.report.core; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "fineract.apachecon.report") +public class JasperReportingProperties { + + private Boolean enabled = true; + private String virtualizerType = "FILE"; +} diff --git a/custom/apachecon/report/migration/build.gradle b/custom/apachecon/report/migration/build.gradle new file mode 100644 index 00000000000..943863e6f4e --- /dev/null +++ b/custom/apachecon/report/migration/build.gradle @@ -0,0 +1,23 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Jasper Reporting Process Migration' + +group = 'org.apache.fineract.apachecon.infrastructure.report' + +archivesBaseName = 'fineract-custom-jasper-report-migration' diff --git a/custom/apachecon/report/migration/src/main/resources/db/custom-changelog/0001_apachecon_report_initial.xml b/custom/apachecon/report/migration/src/main/resources/db/custom-changelog/0001_apachecon_report_initial.xml new file mode 100644 index 00000000000..401cb9cdc64 --- /dev/null +++ b/custom/apachecon/report/migration/src/main/resources/db/custom-changelog/0001_apachecon_report_initial.xml @@ -0,0 +1,555 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/custom/apachecon/report/service/build.gradle b/custom/apachecon/report/service/build.gradle new file mode 100644 index 00000000000..f66535ff974 --- /dev/null +++ b/custom/apachecon/report/service/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Jasper Reporting Process Service' + +group = 'org.apache.fineract.apachecon.infrastructure.report' + +archivesBaseName = 'fineract-custom-jasper-report-service' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/report/service/dependencies.gradle b/custom/apachecon/report/service/dependencies.gradle new file mode 100644 index 00000000000..630a8b13279 --- /dev/null +++ b/custom/apachecon/report/service/dependencies.gradle @@ -0,0 +1,38 @@ +/** + * 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. + */ +dependencies { + compileOnly( + 'org.springframework.boot:spring-boot-autoconfigure', + 'jakarta.ws.rs:jakarta.ws.rs-api', + 'jakarta.annotation:jakarta.annotation-api', + ) + implementation( + project(':fineract-core'), + project(':fineract-provider'), + project(':custom:apachecon:report:core'), + 'org.springframework.data:spring-data-commons', + 'net.sf.jasperreports:jasperreports:6.21.3', + 'net.sf.jasperreports:jasperreports-functions:6.21.3', + 'net.sf.jasperreports:jasperreports-chart-themes:6.21.3', + 'net.sf.jasperreports:jasperreports-chart-customizers:6.21.3', + 'net.sf.jasperreports:jasperreports-custom-visualization:6.21.3', + 'net.sf.jasperreports:jasperreports-annotation-processors:6.21.3', + 'net.sf.jasperreports:jasperreports-metadata:6.21.3', + ) +} diff --git a/custom/apachecon/report/service/src/main/java/org/apache/fineract/apachecon/report/service/JasperReportingProcessService.java b/custom/apachecon/report/service/src/main/java/org/apache/fineract/apachecon/report/service/JasperReportingProcessService.java new file mode 100644 index 00000000000..115e842c78e --- /dev/null +++ b/custom/apachecon/report/service/src/main/java/org/apache/fineract/apachecon/report/service/JasperReportingProcessService.java @@ -0,0 +1,234 @@ +/** + * 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.apachecon.report.service; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.TEXT_HTML; +import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN; + +import jakarta.annotation.PostConstruct; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.sql.Connection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.sf.jasperreports.engine.JRAbstractExporter; +import net.sf.jasperreports.engine.JRParameter; +import net.sf.jasperreports.engine.JasperCompileManager; +import net.sf.jasperreports.engine.JasperFillManager; +import net.sf.jasperreports.engine.export.HtmlExporter; +import net.sf.jasperreports.engine.export.JRCsvExporter; +import net.sf.jasperreports.engine.export.JRPdfExporter; +import net.sf.jasperreports.engine.export.JRTextExporter; +import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter; +import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter; +import net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer; +import net.sf.jasperreports.engine.fill.JRFileVirtualizer; +import net.sf.jasperreports.engine.fill.JRGzipVirtualizer; +import net.sf.jasperreports.engine.fill.JRSwapFileVirtualizer; +import net.sf.jasperreports.engine.util.JRSwapFile; +import net.sf.jasperreports.engine.xml.JRXmlLoader; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.apachecon.report.core.JasperReportingProperties; +import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType; +import org.apache.fineract.infrastructure.report.annotation.ReportService; +import org.apache.fineract.infrastructure.report.service.ReportingProcessService; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@ReportService(type = "Jasper") +@Service +public class JasperReportingProcessService implements ReportingProcessService { + + private static final List ALLOWED_OUTPUT_TYPES = List.of("HTML", "PDF", "XLS", "XLSX", "CSV", "TXT", "DOC", "DOCX"); + + public static final String VITUALIZER_FILE = "file"; + + public static final String VITUALIZER_SWAP = "swap"; + + public static final String VITUALIZER_GZIP = "gzip"; + + private final DataSource dataSource; + + private final JasperReportingProperties jasperReportingProperties; + + @PostConstruct + public void init() { + log.warn(">>>>>>>>>>>>>>>>>> Community over Code 2024: Jasper Reporting Service!!!"); + } + + @Override + @SneakyThrows + public Response processRequest(String templateName, MultivaluedMap queryParams) { + final var outputTypeParam = queryParams.getFirst("output-type"); + final var locale = ApiParameterHelper.extractLocale(queryParams); + + var format = "HTML"; + + if (StringUtils.isNotBlank(outputTypeParam)) { + format = outputTypeParam.toUpperCase(); + } + + if (!ALLOWED_OUTPUT_TYPES.contains(format)) { + throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + format); + } + + String template = templateName + ".jrxml"; + + if (locale != null && !"en".equalsIgnoreCase(locale.toString())) { + template = templateName + "_" + locale.toString().toLowerCase() + ".jrxml"; + } + + log.warn("Report path: {}", template); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + generate(dataSource.getConnection(), template, getReportParams(queryParams), format, os); + + return Response.ok().entity(os.toByteArray()).type(getContentType(format)).build(); + } + + @Override + public List getAvailableExportTargets() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Map getReportParams(MultivaluedMap queryParams) { + final Map reportParams = new HashMap<>(); + for (final Map.Entry> entry : queryParams.entrySet()) { + if (entry.getKey().startsWith("R_")) { + reportParams.put(entry.getKey().substring(2), entry.getValue().get(0)); + } + } + return reportParams; + } + + private void generate(Connection connection, String template, Map parameters, String format, OutputStream os) { + if (template != null) { + JRAbstractLRUVirtualizer virtualizer = null; + + switch (jasperReportingProperties.getVirtualizerType()) { + case VITUALIZER_FILE: + virtualizer = new JRFileVirtualizer(2, jasperReportingProperties.getVirtualizerType()); + virtualizer.setReadOnly(true); + parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); + break; + case VITUALIZER_SWAP: + virtualizer = new JRSwapFileVirtualizer(2, new JRSwapFile("tmp", 1024, 1024), true); + virtualizer.setReadOnly(true); + parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); + break; + case VITUALIZER_GZIP: + virtualizer = new JRGzipVirtualizer(2); + virtualizer.setReadOnly(true); + parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); + break; + default: + log.debug("No report virtualizer set."); + } + + try { + final var jasperDesign = JRXmlLoader + .load(JasperReportingProcessService.class.getClassLoader().getResourceAsStream("reports/" + template)); + final var jasperReport = JasperCompileManager.compileReport(jasperDesign); + final var jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, connection); + + final var exporter = getExporter(format); + exporter.setExporterInput(new SimpleExporterInput(jasperPrint)); + exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(os)); + exporter.exportReport(); + + os.flush(); + os.close(); + } catch (Exception ex) { + log.error("Report export error: ", ex); + } finally { + try { + if (virtualizer != null) { + virtualizer.cleanup(); + } + } catch (Throwable ex) { + // ignore + } + } + } + } + + private JRAbstractExporter getExporter(String format) { + switch (format.toUpperCase()) { + case "PDF": + return new JRPdfExporter(); + case "HTML": + var exporter = new HtmlExporter(); + // exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, imageUrl); + // exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, Boolean.FALSE); + // exporter.setParameter(JRHtmlExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.FALSE); + // exporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "px"); + return exporter; + case "DOC": + case "DOCX": + return new JRDocxExporter(); + case "XLS": + case "XLSX": + return new JRXlsxExporter(); + case "TXT": + return new JRTextExporter(); + case "CSV": + return new JRCsvExporter(); + default: + throw new RuntimeException("Report output format not supported: " + format); + } + } + + private String getContentType(String format) { + switch (format.toUpperCase()) { + case "PDF": + return APPLICATION_JSON; + case "HTML": + return TEXT_HTML; + case "DOC": + return "application/msword"; + case "DOCX": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case "XLS": + return "application/vnd.ms-excel"; + case "XLSX": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case "TXT": + return TEXT_PLAIN; + case "CSV": + return "text/csv"; + default: + throw new RuntimeException("Report output format not supported: " + format); + } + } +} diff --git a/custom/apachecon/report/service/src/main/resources/reports/Client Listing(Jasper).jrxml b/custom/apachecon/report/service/src/main/resources/reports/Client Listing(Jasper).jrxml new file mode 100644 index 00000000000..52dd1d0313c --- /dev/null +++ b/custom/apachecon/report/service/src/main/resources/reports/Client Listing(Jasper).jrxml @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + <band height="68" splitType="Stretch"> + <frame> + <reportElement mode="Opaque" x="-20" y="0" width="597" height="65" forecolor="#006699" backcolor="#006699" uuid="29f958b3-9df5-4ee0-905e-9fd33029926f"/> + <staticText> + <reportElement x="20" y="19" width="554" height="30" forecolor="#FFFFFF" uuid="bb4558e1-0cca-422f-a2eb-88e8eeb8c241"> + <property name="com.jaspersoft.studio.unit.y" value="pixel"/> + <property name="com.jaspersoft.studio.unit.height" value="pixel"/> + </reportElement> + <textElement textAlignment="Center" verticalAlignment="Middle"> + <font size="22" isBold="true"/> + </textElement> + <text><![CDATA[Fineract Client List]]></text> + </staticText> + <image> + <reportElement isPrintRepeatedValues="false" x="21" y="2" width="59" height="65" uuid="fdc83025-e841-44e3-a893-483c783bcb02"/> + <imageExpression><![CDATA["https://fineract.apache.org/images/apache-fineract-logo.png"]]></imageExpression> + </image> + </frame> + </band> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/custom/apachecon/report/starter/build.gradle b/custom/apachecon/report/starter/build.gradle new file mode 100644 index 00000000000..3869eafabd4 --- /dev/null +++ b/custom/apachecon/report/starter/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'Apache Community over Code EU 2024: Jasper Reporting Process Starter' + +group = 'org.apache.fineract.apachecon.infrastructure.report' + +archivesBaseName = 'fineract-custom-jasper-report-starter' + +apply from: 'dependencies.gradle' diff --git a/custom/apachecon/report/starter/dependencies.gradle b/custom/apachecon/report/starter/dependencies.gradle new file mode 100644 index 00000000000..67890a74e32 --- /dev/null +++ b/custom/apachecon/report/starter/dependencies.gradle @@ -0,0 +1,27 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':custom:apachecon:report:core'), + project(':custom:apachecon:report:service'), + project(':custom:apachecon:report:migration'), + 'org.springframework.boot:spring-boot-starter', + ) +} diff --git a/custom/apachecon/report/starter/src/main/java/org/apache/fineract/apachecon/report/starter/JasperReportingAutoConfiguration.java b/custom/apachecon/report/starter/src/main/java/org/apache/fineract/apachecon/report/starter/JasperReportingAutoConfiguration.java new file mode 100644 index 00000000000..31f84dd8719 --- /dev/null +++ b/custom/apachecon/report/starter/src/main/java/org/apache/fineract/apachecon/report/starter/JasperReportingAutoConfiguration.java @@ -0,0 +1,31 @@ +/** + * 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.apachecon.report.starter; + +import org.apache.fineract.apachecon.report.core.JasperReportingProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; + +@AutoConfiguration +@EnableConfigurationProperties({ JasperReportingProperties.class }) +@ComponentScan("org.apache.fineract.apachecon.report") +@ConditionalOnProperty(value = "fineract.apachecon.report.enabled", havingValue = "true") +public class JasperReportingAutoConfiguration {} diff --git a/custom/apachecon/report/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/apachecon/report/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..7ac29b099fc --- /dev/null +++ b/custom/apachecon/report/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.apache.fineract.apachecon.report.starter.JasperReportingAutoConfiguration \ No newline at end of file diff --git a/custom/docker/build.gradle b/custom/docker/build.gradle index 28b9415f366..a691b3d7757 100644 --- a/custom/docker/build.gradle +++ b/custom/docker/build.gradle @@ -16,15 +16,39 @@ * specific language governing permissions and limitations * under the License. */ +import com.bmuschko.gradle.docker.tasks.image.Dockerfile +import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage +import com.bmuschko.gradle.docker.tasks.image.DockerPushImage + description = 'Fineract Custom Docker Image' apply plugin: 'java' apply plugin: 'com.google.cloud.tools.jib' +apply plugin: 'com.bmuschko.docker-remote-api' apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle" +tasks.create("dockerCreateDockerfile", Dockerfile) { + from("azul/zulu-openjdk-alpine:21.0.3-jre") + label(["maintainer": "Aleksandar Vidakovic "]) + instruction 'RUN apk add --no-cache msttcorefonts-installer fontconfig' + instruction 'RUN update-ms-fonts' +} + +task dockerBuildImage(type: DockerBuildImage) { + images.add('monkeysintown/fineract-base:latest') + + dependsOn dockerCreateDockerfile +} + +task dockerPushImage(type: DockerPushImage) { + images.add('monkeysintown/fineract-base:latest') + + dependsOn dockerBuildImage +} + jib { from { - image = 'azul/zulu-openjdk-alpine:17' + image = 'monkeysintown/fineract-base:latest' platforms { platform { architecture = System.getProperty("os.arch").equals("aarch64")?"arm64":"amd64" @@ -34,7 +58,7 @@ jib { } to { - image = 'fineract-custom' + image = 'monkeysintown/fineract-apachecon-2024' tags = [ "${project.version}", 'latest' @@ -60,6 +84,12 @@ jib { dependencies { implementation project(':fineract-core') implementation project(':fineract-provider') + implementation project(':fineract-accounting') + implementation project(':fineract-branch') + implementation project(':fineract-document') + implementation project(':fineract-investor') + implementation project(':fineract-loan') + implementation project(':fineract-savings') // NOTE: dynamically load all custom modules file("${rootDir}/custom").eachDir { companyDir -> if('build' != companyDir.name && 'docker' != companyDir.name) { diff --git a/docker-compose-apachecon-2024.yml b/docker-compose-apachecon-2024.yml new file mode 100644 index 00000000000..9d6cf19a638 --- /dev/null +++ b/docker-compose-apachecon-2024.yml @@ -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. +# + +version: "3.8" +services: + db: + extends: + file: ./config/docker/compose/postgresql.yml + service: postgresql + + redis: + image: redis:6.2-alpine + ports: + - "6379:6379" + + fineract: + image: monkeysintown/fineract-apachecon-2024: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 + ports: + - "8443:8443" + depends_on: + db: + condition: service_healthy + env_file: + - ${PWD}/config/docker/env/fineract.env + - ${PWD}/config/docker/env/fineract-common.env + - ${PWD}/custom/apachecon/common/configuration/apachecon.env + - ${PWD}/config/docker/env/fineract-postgresql.env + + community-app: + image: openmf/community-app:latest + container_name: mifos-ui + ports: + - "9090:80" 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 3df570b1fa3..f2cd990388a 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 @@ -56,10 +56,12 @@ import org.apache.fineract.infrastructure.hooks.event.HookEventSource; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; @Service +@ConditionalOnMissingBean(CommandProcessingService.class) @Slf4j @RequiredArgsConstructor public class SynchronousCommandProcessingService implements CommandProcessingService { diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 38433c3ab41..84eee0e54b8 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -97,6 +97,8 @@ dependencies { 'com.github.spotbugs:spotbugs-annotations', 'io.swagger.core.v3:swagger-annotations-jakarta', + 'io.swagger.core.v3:swagger-jaxrs2-jakarta', + 'io.swagger.core.v3:swagger-core-jakarta', 'com.google.cloud.sql:mysql-socket-factory-connector-j-8', 'com.squareup.retrofit2:converter-gson', @@ -113,8 +115,6 @@ dependencies { exclude group: 'commons-logging' } - 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' exclude group: 'javax.activation', module: 'activation' diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java index 498e3b0644f..b9278d73f06 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java @@ -138,8 +138,11 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } if (!FIRST_REQUEST_PROCESSED) { - final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/"); - System.setProperty("baseUrl", baseUrl); + // TODO(apachecon): this should be covered by configuration: + // baseUrl=https://localhost:8443${FINERACT_SERVER_SERVLET_CONTEXT_PATH:/fineract-provider} + // + // final var baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/"); + // System.setProperty("baseUrl", baseUrl); final boolean ehcacheEnabled = configurationDomainService.isEhcacheEnabled(); if (ehcacheEnabled) { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491770..e6441136f3d 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 a80b22ce5cf..cee8b1b9a0a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-rc-2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42690..b740cf13397 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/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/.