diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 00000000000..e98fbf598f6 --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,25 @@ +name: Fineract Docker build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'zulu' + - name: Build the image + run: ./gradlew :fineract-provider:clean :fineract-provider:jibDockerBuild -x test + - name: Start the stack + run: docker-compose up -d + - name: Wait for stack to come up + run: sleep 60 + - name: Check health + run: curl -f -k --retry 5 --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.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..7c14e86b921 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,67 @@ +name: Fineract Gradle build - basicauth +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-20.04 + + services: + mariad: + image: mariadb:10.6 + ports: + - 3306:3306 + env: + MARIADB_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:0.4.0 + 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 + steps: + - name: Set up cache + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'zulu' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - 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: Install additional software + run: | + sudo apt-get update + sudo apt-get install ghostscript -y + + - name: Basic Auth Build & Test + run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test + + - name: 2FA Build & Test + run: ./gradlew --no-daemon -q --console=plain :twofactor-tests:test --fail-fast + + - name: OAuth2 Build & Test + run: ./gradlew --no-daemon -q --console=plain :oauth2-tests:test --fail-fast diff --git a/.github/workflows/fineract.dev.yaml b/.github/workflows/fineract.dev.yaml index 26f26aea796..e774331defc 100644 --- a/.github/workflows/fineract.dev.yaml +++ b/.github/workflows/fineract.dev.yaml @@ -22,15 +22,21 @@ jobs: runs-on: ubuntu-latest steps: - - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master + - uses: actions/checkout@v2 + + - id: 'auth' + uses: google-github-actions/auth@v0 with: - project_id: ${{ secrets.GOOGLE_PROJECT_ID }} - service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS}} + credentials_json: '${{ secrets.GOOGLE_APPLICATION_CREDENTIALS}}' - - uses: actions/checkout@v2 + - uses: google-github-actions/setup-gcloud@v0 + with: + project_id: ${{ secrets.GOOGLE_PROJECT_ID }} - run: gcloud components install beta - run: JOBS=$(gcloud beta builds list --ongoing --format="value[terminator=' '](id)") && [ ! -z "$JOBS" ] && echo "Cancelling $JOBS..." && gcloud builds cancel $JOBS --no-user-output-enabled || true - - run: gcloud beta builds triggers run deploy-demo --branch master --format=none + - run: gcloud beta builds triggers list + + - run: gcloud beta builds triggers run deploy-demo --branch=master diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 405a2b30659..00000000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Validate Gradle Wrapper" -on: [push, pull_request] - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 00000000000..98598838f7a --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,32 @@ +name: Fineract Sonarqube +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-20.04 + env: + TZ: Asia/Kolkata + SONAR_ORGANIZATION: apache + SONAR_HOST_URL: https://sonarcloud.io + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - name: Set up cache + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'zulu' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - 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 --info -x rat -x test -x spotbugsMain -x spotbugsTest -x spotbugsGenerated -x licenseMain -x licenseTest -x checkstyleMain -x checkstyleTest --info --stacktrace build sonarqube diff --git a/.gitignore b/.gitignore index a7cbdec2c3d..0ae3f43739b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,14 +10,15 @@ build *.iml *.ipr *.iws +*.swp *.DS_Store .idea .vscode catalina.base_IS_UNDEFINED/ keystore.jks bin/ -fineract-provider/src/main/resources/application.properties .lock fineract-provider/out/ fineract-provider/config/swagger/config.json fineract-provider/config/swagger/fineract-input.yaml +licenses diff --git a/.travis.yml b/.travis.yml index 5d5d4f067cb..f571e23f5aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,36 +23,33 @@ dist: bionic language: generic -services: - - mysql - - docker - addons: - apt: - packages: - - curl - - graphviz - - gv + sonarcloud: + organization: "apache" + token: + secure: "Q2E9ijK+9+R6nkb9o2FQrKUiAozp0325mlGfM25rGV3mCRPyWR/lU8o48r2eaBEX8SEvMvXIO3XXD9SPyUX2+svLz0MOMdfZfNveg6jmcHNRjkzzBK0bMIIGbEH/YqJ+psl4nDmlDd6IK4685/1JSRUJLqeKKK2ulixm6PXcmwZWeKD6TjyXsW50zz3yYIOuROHzys/M6H05h1dZD3j6ztnRsjbr7SmqFEVWSRgEd/aAs+HZB9Fv1xN66csRoQUQKgzoBO9egAQcel65Av0Bf+pOQuzVDVyXNohZEYia02MCGlC6OFvtL7xIXGf/vo02RdnlN4Jnma6P37TaNrmpk2e+EGJaZHmL9cb98+Zc39uPb7qEZkMfDdKrW6QVRtScSuCJzpY58WRgTS0jLCxsDBAMfsrIvWbHntFZL9q5aEkFgecj0pg9u6WHxgA8cuPUY5QzHQo9fEUxNJHGJHQ2FkafLniqJp7DVbsyQHmEggaTrGGrketeYvCbSR1oOnBgImUwzjIV9/zOdd10Wzi8x9HsmzrGBPSBaO4fBxjC1LZwdyue2a2+oKvBqpAGcLpGaCRfHTh2yPXhZW4bw11rOmQxgtOQVBXhveciWAUQuVXIak+RZnu/zRvnul9sZ6bb1hEUPkpUypk+FDeLm8T7y2A++u/jBam2vLc0iff5xtA=" # See https://issues.apache.org/jira/browse/FINERACT-937 for background re. what below is all about.. # We basically remove the outdated OpenJDK 11.0.2 which Travis image grabbed from java.net in # https://github.com/travis-ci/travis-cookbooks/blob/master/cookbooks/travis_jdk/files/install-jdk.sh#L200 -# (which has 11.0.2 hard-coded!), and instead replace it with the latest openjdk-11-jdk-headless Ubuntu package. +# (which has 11.0.2 hard-coded!), and instead replace it with the latest Azul Zulu Debian package. # NB /usr/local/lib/jvm/openjdk11/bin is also on the PATH, but there's no need to change it (because we rm -rf anyway) +# Installing Azul Zulu as per official instructions https://docs.azul.com/core/zulu-openjdk/install/debian before_install: - echo $JAVA_HOME - which java - sudo rm -rf /usr/local/lib/jvm/ - - sudo apt-get install -y openjdk-11-jdk-headless - - export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/ - - which java - - java -version - - $JAVA_HOME/bin/java -version - - echo "USE mysql;\nALTER USER 'root'@'localhost' IDENTIFIED BY 'mysql';\n" | mysql -u root - - mysql -u root -pmysql -e 'CREATE DATABASE IF NOT EXISTS `fineract_tenants`;' - - mysql -u root -pmysql -e 'CREATE DATABASE IF NOT EXISTS `fineract_default`;' -# Hardcoding the time zone is a temporary fix for https://issues.apache.org/jira/browse/FINERACT-723 - - export TZ=Asia/Kolkata + +install: + - unset _JAVA_OPTIONS + - sudo apt-get -q update + - sudo apt-get -yq install gnupg curl dirmngr + - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0xB1998361219BD9C9 + - curl -O https://cdn.azul.com/zulu/bin/zulu-repo_1.0.0-3_all.deb + - sudo apt-get -yq install ./zulu-repo_1.0.0-3_all.deb + - sudo apt-get -q update + - sudo apt-get -yq install zulu17-jdk + - export JAVA_HOME=/usr/lib/jvm/zulu17 # https://docs.travis-ci.com/user/languages/java/#caching before_cache: @@ -64,16 +61,20 @@ cache: - $HOME/.gradle/wrapper/ - $HOME/.m2 -# NOTE: We used to run with --info, which is quite a bit more verbose, but is VERY useful to understand failures on Travis, -# where you do not have access to any files like build/reports/tests/index.html, only the Console. -# @see http://mrhaki.blogspot.ch/2013/05/gradle-goodness-show-more-information.html -# @see http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging for alternative -# https://jira.apache.org/jira/browse/FINERACT-732 removed that again, because it made Travis CI fail. +# Check Java environment variables +before_script: + - echo $JDK_JAVA_OPTIONS + - echo $JAVA_OPTIONS + - echo $JAVA_HOME + - which java + - java -version + - which javac + - javac -version + - $JAVA_HOME/bin/java -version + script: - date -# using "&&" instead of several "-" means that integrationTest does not run if test fails, -# and Docker test does not run if integration test fails, which makes PR failure easier to understand. -# @see https://docs.travis-ci.com/user/job-lifecycle/#customizing-the-build-phase - - ./gradlew --no-daemon --console=plain licenseMain licenseTest check build test --fail-fast doc && sudo service mysql stop && docker-compose build && docker-compose up -d && sleep 60s && curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health && (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) -# We stop the mysql system service when running the Docker test to avoid port 3306 conflicts (unless we run the mysql in docker-compose on another port; req. FINERACT-773) -# The fancy /actuator/info test makes sure that has more than 100 characters of JSON to test that the git.properties worked (see FINERACT-983) +# Using travis_wait for a proccess that could take longer than 20 minutes, in this case the SonaQube analysis +# https://docs.travis-ci.com/user/common-build-problems/#build-times-out-because-no-output-was-received + - travis_wait 30 ./gradlew -q --no-daemon --console=plain -x rat -x test sonarqube -Pcoverage=true + - date diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 361d029722d..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -FROM openjdk:11 AS builder - -RUN apt-get update -qq && apt-get install -y wget - -COPY . fineract -WORKDIR /fineract - -RUN ./gradlew --no-daemon -q -x rat -x compileTestJava -x test -x spotlessJavaCheck -x spotlessJava bootJar - -# https://issues.apache.org/jira/browse/LEGAL-462 -# https://issues.apache.org/jira/browse/FINERACT-762 -# We include an alternative JDBC driver (which is faster, but not allowed to be default in Apache distribution) -# allowing implementations to switch the driver used by changing start-up parameters (for both tenants and each tenant DB) -# The commented out lines in the docker-compose.yml illustrate how to do this. -WORKDIR /fineract/libs -RUN wget -q https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.23/mysql-connector-java-8.0.23.jar - -# ========================================= - -FROM gcr.io/distroless/java:11 as fineract - -COPY --from=builder /fineract/fineract-provider/build/libs /app -COPY --from=builder /fineract/libs /app/libs - -WORKDIR /app - -ENTRYPOINT ["java", "-Dloader.path=/app/libs/", "-jar", "/app/fineract-provider.jar"] diff --git a/LICENSE_RELEASE b/LICENSE_RELEASE index 2c50915cdef..de45a0587be 100644 --- a/LICENSE_RELEASE +++ b/LICENSE_RELEASE @@ -216,92 +216,9 @@ and resource files are also released under the ASF license above. ================================================== *The exceptions for binary release are as follows: ================================================== -This product bundles AOP Alliance v1.0, which is available under a -Public Domain license. For details, see licenses/binary/Aopa.PL - -This product bundles ASM Core v3.1, which is available under a BSD license. -For details, see licenses/binary/Asm.BSD - -This product bundles JavaMail API(compat) v1.5.0-b01, -which is available under a CDDL v1.0 license. -For details, see licenses/binary/Javamail.CDDL - -This product bundles AspectJ Runtime v1.8.2, Copyright (c) 1999-2001 -Xerox Corporation, 2002 Palo Alto Research Center, Incorporated (PARC), -which is available under a EPL v1.0 license. -For details, see licenses/binary/Aspectrt.EPL - -This product bundles AspectJ Weaver v1.8.2, Copyright (c) 2002 Palo Alto -Research Center, Incorporated (PARC), which is available under a EPL v1.0 -license. For details, see licenses/binary/Aspectweaver.EPL - -This product bundles IText(A Free Java PDF Library) v2.1.7, Copyright 2007 -Bruno Lowagie, which is available under a MPL v1.1 license. -For details, see licenses/binary/IText.MPL - -This product bundles DOM4J, Copyright 2001-2005 (C) MetaStuff, Ltd., -which is available under a BSD license. -For details, see licenses/binary/Dom4J.BSD - -This product bundles JDBC driver for MySQL and Drizzle v1.3, Copyright (c) -2009-2013, Marcus Eriksson which is available under a BSD license. -For details, see licenses/binary/Drizzle.BSD - -This product bundles Ical4J v1.0.4, Copyright (c) 2012, Ben Fortuna, -which is available under a BSD license. -For details, see licenses/binary/Ical4J.BSD - -This product bundles Jersey Core v1.17, Copyright (c) 2010-2012 Oracle -and/or its affiliates, which is available under a CDDL 1.1 license. -For details, see licenses/binary/Jersey.CDDL - -This product bundles Jersey Json v1.17, Copyright (c) 2010-2012 Oracle -and/or its affiliates, which is available under a CDDL 1.1 license. -For details, see licenses/binary/Jersey.CDDL - -This product bundles Jersey Multipart v1.17, Copyright (c) 2010-2011 Oracle -and/or its affiliates, which is available under a CDDL 1.1 license. -For details, see licenses/binary/Jersey.CDDL - -This product bundles Jersey Server v1.17, Copyright (c) 2010-2011 Oracle -and/or its affiliates, which is available under a CDDL 1.1 license. -For details, see licenses/binary/Jersey.CDDL - -This product bundles Jersey Servlet v1.17, Copyright (c) 2010-2011 Oracle -and/or its affiliates, which is available under a CDDL 1.1 license. -For details, see licenses/binary/Jersey.CDDL - -This product bundles Jersey Spring v1.17, Copyright (c) 2010-2011 Oracle -and/or its affiliates, which is available under a CDDL 1.1 license. -For details, see licenses/binary/Jersey.CDDL - -This product bundles Logback Classic Module v1.1.2, Copyright (C) 1999-2012, -QOS.ch. All rights reserved, which is available under a EPL v1.0 license. -For details, see licenses/binary/Logback.EPL - -This product bundles Logback Core Module v1.1.2, Copyright (C) 1999-2012, -QOS.ch. All rights reserved, which is available under a EPL v1.0 license. -For details, see licenses/binary/Logback.EPL - -This product bundles MIME Streaming Extension v1.6, Copyright (c) 1997-2010 -Oracle and/or its affiliates. All rights reserved., which is available under -a CDDL v1.1 license. -For details, see licenses/binary/Mimepull.CDDL - -This product bundles JUnit v3.8.1 created by Erich Gamma and Kent Beck which -is available under a CPL v1.0 license. -For details, see licenses/binary/Junit.CPL - -This product bundles SLF4J API Module v1.7.7 opyright (c) 2004-2013 QOS.ch -All rights reserved., which is available under a MIT license. -For details, see licenses/binary/Slf4j.MIT - -This product bundles Backport Util Concurrent v3.1 written by Dawid Kurzyniec, -which is avilable under Public Domain license. -For details see licenses/binary/Backport.PL - -This product bundles JarAnalyzer v1.2 written by Kirk Knoernschild, -which is available under BSD license. -For details see licences/binary/JarAnalyzer.BSD - -****************************************** \ No newline at end of file + +This product bundles third party jars and resource files that are +released under other licenses than the ASF license. You can find +details of all third party licenses in the directory "licenses". + +****************************************** diff --git a/LICENSE_SOURCE b/LICENSE_SOURCE index 868828a0d7f..ffb67c50233 100644 --- a/LICENSE_SOURCE +++ b/LICENSE_SOURCE @@ -217,70 +217,11 @@ and resource files are also released under the ASF license above. *The exceptions for source release are as follows: ================================================== -For API & System Architecture Documentation Apache Fineract includes -following css and js packages. - -This product bundles normalize v1.1.1 and v2.1.0, copyright Nicolas -Gallagher and Jonathan Neal, which is available under a MIT license. -For details, see licenses/source/normalize.MIT - -This product bundles Modernizr v2.6.2, which is available under a MIT license. -For details, see licenses/source/modernizr.MIT - -This product bundles filesaver, copyright Eli Grey, which is available under -a MIT license. -For details, see licenses/source/filesaver.MIT - -This product bundles Glyphicons Halflings Regular, copyright Jan Kovarik, -which is available under a MIT license. -For details, see licenses/source/glyphicons.MIT - -This product bundles jszip, copyright Stuart Knightley, -David Duponchel, Franz Buchinger and António Afonso, which is available -under a MIT license. -For details, see licenses/source/jszip.MIT +For API Documentation for Apache Fineract includes +following css and js packages: This product bundles jQuery v1.7, v1.7.1, Copyright (c) 2011 John Resig, which is available under a MIT license. -For details, see licenses/source/jQuery1.MIT - -This product bundles jQuery v1.9.1, v1.10.2, copyright jQuery Foundation, -Inc. which is available under a MIT license. -For details, see licenses/source/jQuery.MIT - -This product bundles Respond v1.1.0, copyright Scott Jehl, -which is available under a MIT license. -For details, see licenses/source/respond.MIT - -This product bundles matchMedia polyfill, copyright Scott Jehl, -which is available under a MIT license. -For details, see licenses/polyfill.MIT +For details, see https://jquery.org/license/ -This product bundles HTML5Shiv v3.6.2, copyright Alexander Farkas, -which is available under a MIT license. -For details, see licenses/source/html5shiv.MIT - -This product bundles UglifyJS copyright Mihai Bazon, -which available under a BSD license. -For details, see licenses/source/uglify.BSD - -This product bundles sizzle copyright jQuery Foundation and other -contributors, which is available under a MIT license. -For details, see licenses/source/sizzle.MIT - -This product bundles some parts of es5-shim, copyright Kristopher Michael -Kowal and contributors, which is available under a MIT license. -For details, see licenses/source/es5shim.MIT - -This product bundles toc v0.1.2, copyright Greg Allen, -which is available under a MIT license. -For details, see licenses/source/toc.MIT - -This product bundles livejs Version 4, copyright Martin Kool and Q42, -which is available under a MIT license. -For details, see licenses/source/livejs.MIT - -This product bundles HTML5 Boilerplate, which is under a MIT license. -For details, see licenses/source/h5bp.MIT. - -****************************************** \ No newline at end of file +****************************************** diff --git a/NOTICE_RELEASE b/NOTICE_RELEASE index 55783b4a388..10dfd7f96ad 100644 --- a/NOTICE_RELEASE +++ b/NOTICE_RELEASE @@ -1,79 +1,9 @@ Apache Fineract -Copyright 2008-2016 The Apache Software Foundation +Copyright 2008-2021 The Apache Software Foundation This product includes software developed by The Apache Software Foundation (http://www.apache.org/). -Apache Fineract - notice for binary distribution -========================================== -Apache Fineract includes JavaMail API(compat) v1.5.0-b01. -Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved. -Fineract elects to include this software in this distribution under the -CDDL license. You can obtain a copy of the License at: -https://glassfish.java.net/public/CDDL+GPL.html - -Apache Fineract includes all Jersey libraries (Jersey Core v1.17, -Jersey Json v1.17, Jersey Multipart v1.17, Jersey Server v1.17, -Jersey Servlet v1.17, Jersey Spring v1.17). -Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved. -Fineract elects to include this software in this distribution under the -CDDL license. You can obtain a copy of the License at: -https://glassfish.java.net/public/CDDL+GPL.html - -Apache Fineract includes Logback Classic Module v1.1.2 and Logback Core -Module v1.1.2, Copyright (C) 1999-2012, QOS.ch. -All rights reserved. Fineract elects to include this software in this -distribution under the EPL license. You can obtain a copy of the License at: -http://www.eclipse.org/legal/epl-v10.html - -Apache Fineract includes MIME Streaming Extension v1.6, -Copyright (c) 1997-2010 Oracle and/or its affiliates. -All rights reserved. Fineract elects to include this software in this -distribution under the CDDL license. You can obtain a copy of the License at: -https://glassfish.java.net/public/CDDL+GPL_1_1.html - -Apache Fineract includes JUnit v3.8.1 created by Erich Gamma and Kent Beck. -Fineract elects to include this software in this distribution under the -CPL license. You can obtain a copy of the License at: -https://opensource.org/licenses/cpl1.0.txt - -Apache Fineract includes Log4j Implemented Over SLF4J. Fineract elects to -include this software in this distribution under the Apache license. -You can obtain a copy of the License at: -http://apache.org/licenses/LICENSE-2.0 - -Apache Fineract includes Jackson Core v2.3.4, Jackson Annotations v2.3.0, -Jackson Databind v2.3.4, JAX RS Provider For JSON Content Type v1.9.2, -Xml Compatibility Extensions For Jackson v1.9.2 libraries. -Fineract elects to include this software in this distribution under the -Apache license. You can obtain a copy of the License at: -http://apache.org/licenses/LICENSE-2.0 - -Apache Fineract includes AspectJ Runtime v1.8.2, Copyright (c) 1999-2001 -Xerox Corporation, 2002 Palo Alto Research Center, Incorporated (PARC) and -AspectJ Weaver v1.8.2 Copyright (c) 2002 Palo Alto Research Center, -Incorporated (PARC)., libraries. The library information is available at -(https://eclipse.org/aspectj/) - -Apache Fineract includes IText(A Free Java PDF Library) v2.1.7 library, -Copyright 2007 Bruno Lowagie. The library information is available at -(http://developers.itextpdf.com/itext-java) - -===================== -Apache OpenJPA NOTICE - -OpenJPA includes software developed by the SERP project -Copyright (c) 2002-2006, A. Abram White. All rights reserved. - -OpenJPA includes the persistence and orm schemas from the JPA specifications. -Copyright 2005-2014 Sun Microsystems, Inc. All rights reserved. - -OpenJPA includes the persistence and orm schemas from the JPA specifications. -Copyright 2005-2009 Sun Microsystems, Inc. All rights reserved. -OpenJPA elects to include this software in this distribution under the -CDDL license. You can obtain a copy of the License at: - https://glassfish.dev.java.net/public/CDDL+GPL.html -The source code is available at: - https://glassfish.dev.java.net/source/browse/glassfish/ - -OpenJPA includes software written by Miroslav Nachev \ No newline at end of file +This product includes third party libraries and resource files from +different providers. The details of the different packages and their +respective licenses can be found under "licenses" diff --git a/NOTICE_SOURCE b/NOTICE_SOURCE index 0259047fc7e..57c087f090c 100644 --- a/NOTICE_SOURCE +++ b/NOTICE_SOURCE @@ -1,5 +1,5 @@ Apache Fineract -Copyright 2008-2016 The Apache Software Foundation +Copyright 2008-2021 The Apache Software Foundation This product includes software developed by The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index 1909de225b2..eb8ca68de0f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -Apache Fineract: A Platform for Microfinance [![Build Status](https://travis-ci.org/apache/fineract.svg?branch=develop)](https://travis-ci.org/apache/fineract) [![Swagger Validation](https://validator.swagger.io/validator?url=https://demo.fineract.dev/fineract-provider/swagger-ui/fineract.yaml)](https://validator.swagger.io/validator/debug?url=https://demo.fineract.dev/fineract-provider/swagger-ui/fineract.yaml) [![Docker Hub](https://img.shields.io/docker/pulls/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract) [![Docker Build](https://img.shields.io/docker/cloud/build/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract/builds) +Apache Fineract: A Platform for Microfinance ============ +[![Swagger Validation](https://validator.swagger.io/validator?url=https://demo.fineract.dev/fineract-provider/swagger-ui/fineract.yaml)](https://validator.swagger.io/validator/debug?url=https://demo.fineract.dev/fineract-provider/swagger-ui/fineract.yaml) [![build](https://github.com/apache/fineract/actions/workflows/build.yml/badge.svg)](https://github.com/apache/fineract/actions/workflows/build.yml) [![Docker Hub](https://img.shields.io/docker/pulls/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract) [![Docker Build](https://img.shields.io/docker/cloud/build/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract/builds) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=apache_fineract&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=apache_fineract) -Fineract is a mature platform with open APIs that provides a reliable, robust, and affordable core banking solution for financial institutions offering services to the world’s 2 billion underbanked and unbanked. + +Fineract is a mature platform with open APIs that provides a reliable, robust, and affordable core banking solution for financial institutions offering services to the world’s 3 billion underbanked and unbanked. [Have a look at the FAQ on our Wiki at apache.org](https://cwiki.apache.org/confluence/display/FINERACT/FAQ) if this README does not answer what you are looking for. [Visit our JIRA Dashboard](https://issues.apache.org/jira/secure/Dashboard.jspa?selectPageId=12335824) to find issues to work on, see what others are working on, or open new issues. @@ -18,18 +20,18 @@ If you are interested in contributing to this project, but perhaps don't quite k Requirements ============ -* Java >= 11 (OpenJDK JVM is tested by our CI on Travis) -* MySQL 5.7 +* Java >= 17 (OpenJDK JVM is tested by our CI on Travis) +* MariaDB 10.6 You can run the required version of the database server in a container, instead of having to install it, like this: - docker run --name mysql-5.7 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mysql -d mysql:5.7 + docker run --name mariadb-10.6 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:10.6 and stop and destroy it like this: - docker rm -f mysql-5.7 + docker rm -f mariadb-10.6 -Beware that this database container database keeps its state inside the container and not on the host filesystem. It is lost when you destroy (rm) this container. This is typically fine for development. See [Caveats: Where to Store Data on the database container documentation](https://hub.docker.com/_/mysql) re. how to make it persistent instead of ephemeral. +Beware that this database container database keeps its state inside the container and not on the host filesystem. It is lost when you destroy (rm) this container. This is typically fine for development. See [Caveats: Where to Store Data on the database container documentation](https://hub.docker.com/_/mariadb) re. how to make it persistent instead of ephemeral. Tomcat v9 is only required if you wish to deploy the Fineract WAR to a separate external servlet container. Note that you do not require to install Tomcat to develop Fineract, or to run it in production if you use the self-contained JAR, which transparently embeds a servlet container using Spring Boot. (Until FINERACT-730, Tomcat 7/8 were also supported, but now Tomcat 9 is required.) @@ -46,20 +48,65 @@ Run the following commands: Instructions to build the JAR file ============ 1. Clone the repository or download and extract the archive file to your local directory. -2. Run `./gradlew clean bootJar` to build a modern cloud native fully self contained JAR file which will be created at `build/libs` directory. -3. Start it using `java -jar build/libs/fineract-provider.jar` (does not require external Tomcat) +2. Run `./gradlew clean bootJar` to build a modern cloud native fully self contained JAR file which will be created at `fineract-provider/build/libs` directory. +3. As we are not allowed to include a JDBC driver in the built JAR, download a JDBC driver of your choice. For example: `wget https://downloads.mariadb.com/Connectors/java/connector-java-2.7.3/mariadb-java-client-2.7.3.jar` +4. Start the jar and pass the directory where you have downloaded the JDBC driver as loader.path, for example: `java -Dloader.path=. -jar fineract-provider/build/libs/fineract-provider.jar` (does not require external Tomcat) The tenants database connection details are configured [via environment variables (as with Docker container)](#instructions-to-run-using-docker-and-docker-compose), e.g. like this: - export fineract_tenants_pwd=verysecret + export FINERACT_HIKARI_PASSWORD=verysecret ... java -jar fineract-provider.jar +Security +============ +NOTE: The HTTP Basic and OAuth2 authentication schemes are mutually exclusive. You can't enable them both at the same time. Fineract checks these settings on startup and will fail if more than one authentication scheme is enabled. + +HTTP Basic Authentication +------------ +By default Fineract is configured with a HTTP Basic Authentication scheme, so you actually don't have to do anything if you want to use it. But if you would like to explicitly choose this authentication scheme then there are two ways to enable it: +1. Use environment variables (best choice if you run with Docker Compose): +``` +FINERACT_SECURITY_BASICAUTH_ENABLED=true +FINERACT_SECURITY_OAUTH_ENABLED=false +``` +2. Use JVM parameters (best choice if you run the Spring Boot JAR): +``` +java -Dfineract.security.basicauth.enabled=true -Dfineract.security.oauth.enabled=false -jar fineract-provider.jar +``` + +OAuth2 Authentication +------------ +There is also an OAuth2 authentication scheme available. Again, two ways to enable it: +1. Use environment variables (best choice if you run with Docker Compose): +``` +FINERACT_SECURITY_BASICAUTH_ENABLED=false +FINERACT_SECURITY_OAUTH_ENABLED=true +``` +2. Use JVM parameters (best choice if you run the Spring Boot JAR): +``` +java -Dfineract.security.basicauth.enabled=false -Dfineract.security.oauth.enabled=true -jar fineract-provider.jar +``` + +Two Factor Authentication +------------ +You can also enable 2FA authentication. Depending on how you start Fineract add the following: + +1. Use environment variable (best choice if you run with Docker Compose): +``` +FINERACT_SECURITY_2FA_ENABLED=true +``` +2. Use JVM parameter (best choice if you run the Spring Boot JAR): +``` +-Dfineract.security.2fa.enabled=true +``` + + Instructions to build a WAR file ============ 1. Clone the repository or download and extract the archive file to your local directory. -2. Run `./gradlew clean war` to build a traditional WAR file which will be created at `build/libs` directory. +2. Run `./gradlew :fineract-war:clean :fineract-war:war` to build a traditional WAR file which will be created at `fineract-war/build/libs` directory. 3. Deploy this WAR to your Tomcat v9 Servlet Container. We recommend using the JAR instead of the WAR file deployment, because it's much easier. @@ -83,10 +130,13 @@ Instructions to run and debug in Eclipse IDE It is possible to run Fineract in Eclipse IDE and also to debug Fineract using Eclipse's debugging facilities. To do this, you need to create the Eclipse project files and import the project into an Eclipse workspace: -1. Import the fineract-provider project into your Eclipse workspace (File->Import->Gradle->Existing Gradle Project into Workspace, choose root directory fineract/fineract-provider) -2. Do a clean build of the project in Eclipse (Project->Clean...) +1. Create Eclipse project files into the Fineract project by running `./gradlew cleanEclipse eclipse` +2. Import the fineract-provider project into your Eclipse workspace (File->Import->General->Existing Projects into Workspace, choose root directory fineract/fineract-provider) +3. Do a clean build of the project in Eclipse (Project->Clean...) 3. Run / debug Fineract by right clicking on org.apache.fineract.ServerApplication class and choosing Run As / Debug As -> Java Application. All normal Eclipse debugging features (breakpoints, watchpoints etc) should work as expected. +If you change the project settings (dependencies etc) in Gradle, you should redo step 1 and refresh the project in Eclipse. + You can also use Eclipse Junit support to run tests in Eclipse (Run As->Junit Test) Finally, modifying source code in Eclipse automatically triggers hot code replace to a running instance, allowing you to immediately test your changes @@ -112,7 +162,7 @@ Now to run a new Fineract instance you can simply: 1. `git clone https://github.com/apache/fineract.git ; cd fineract` 1. for windows, use `git clone https://github.com/apache/fineract.git --config core.autocrlf=input ; cd fineract` -1. `docker-compose build` +1. `./gradlew :fineract-provider:jibDockerBuild -x test` 1. `docker-compose up -d` 1. fineract (back-end) is running at https://localhost:8443/fineract-provider/ 1. wait for https://localhost:8443/fineract-provider/actuator/health to return `{"status":"UP"}` @@ -120,8 +170,6 @@ Now to run a new Fineract instance you can simply: 1. community-app (UI) is running at http://localhost:9090/?baseApiUrl=https://localhost:8443/fineract-provider&tenantIdentifier=default 1. login using default _username_ `mifos` and _password_ `password` -The [`docker-compose.yml`](docker-compose.yml) will build the `fineract` container from the source based on the [`Dockerfile`](Dockerfile). You could change that to use the pre-built container image instead of having to re-build it. - https://hub.docker.com/r/apache/fineract has a pre-built container image of this project, built continuously. You must specify the MySQL tenants database JDBC URL by passing it to the `fineract` container via environment @@ -131,6 +179,26 @@ _(Note that in previous versions, the `mysqlserver` environment variable used at and the `mysqlserver` environment variable is now no longer supported.)_ +Connection pool configuration +============================= + +Please check `application.properties` to see which connection pool settings can be tweaked. The associated environment variables are prefixed with `FINERACT_HIKARI_*`. You can find more information about specific connection pool settings (Hikari) at https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby + +NOTE: we'll keep backwards compatibility until one of the next releases to ensure that things are working as expected. Environment variables prefixed `fineract_tenants_*` can still be used to configure the database connection, but we strongly encourage using `FINERACT_HIKARI_*` with more options. + +SSL configuration +================= + +By default SSL is enabled, but all SSL related properties are now tunable. SSL can be turned off by setting the environment variable `FINERACT_SERVER_SSL_ENABLED` to false. If you do that then please make sure to also change the server port to `8080` via the variable `FINERACT_SERVER_PORT`, just for the sake of keeping the conventions. +You can choose now easily a different SSL keystore by setting `FINERACT_SERVER_SSL_KEY_STORE` with a path to a different (not embedded) keystore. The password can be set via `FINERACT_SERVER_SSL_KEY_STORE_PASSWORD`. See the `application.properties` file and the latest Spring Boot documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) for more details. + + +Tomcat configuration +==================== + +Please refer to the `application.properties` and the official Spring Boot documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) on how to do performance tuning for Tomcat. Note: you can set now the acceptable form POST size (default is 2MB) via environment variable `FINERACT_SERVER_TOMCAT_MAX_HTTP_FORM_POST_SIZE`. + + Instructions to run on Kubernetes ================================= @@ -258,7 +326,7 @@ is used in development when running integration tests that use the Flyway librar driver is however not included in and distributed with the Fineract product and is not required to use the product. If you are developer and object to using the LGPL licensed Connector/J JDBC driver, -simply do not run the integration tests that use the Flyway library. +simply do not run the integration tests that use the Flyway library and/or use another JDBC driver. As discussed in [LEGAL-462](https://issues.apache.org/jira/browse/LEGAL-462), this project therefore complies with the [Apache Software Foundation third-party license policy](https://www.apache.org/legal/resolved.html). @@ -392,7 +460,7 @@ Releasing Before you use Gradle to create a release you need to make sure that you provide the proper GPG parameters. You have to options: -1. Provide the parameters via ~/gradle/gradle.properties in your home folder: +1. Provide the parameters via ~/.gradle/gradle.properties in your home folder: ``` signing.gnupg.keyName=7890ABCD signing.gnupg.passphrase=secret @@ -414,23 +482,23 @@ NOTE: Let's assume your GPG key ID would be "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789 Above tasks will create the following files in folder build/distributions: -- binary distribution file: apache-fineract-1.4.0-binary.tar.gz -- ASCII armored signature for binary distribution: apache-fineract-1.4.0-binary.tar.gz.asc -- SHA512 checksum for binary distribution: apache-fineract-1.4.0-binary.tar.gz.sha512 -- source distribution file: apache-fineract-1.4.0-src.tar.gz -- ASCII armored signature for source distribution: apache-fineract-1.4.0-src.tar.gz.asc -- SHA512 checksum for source distribution: apache-fineract-1.4.0-src.tar.gz.sha512 +- binary distribution file: apache-fineract-1.6.0-binary.tar.gz +- ASCII armored signature for binary distribution: apache-fineract-1.6.0-binary.tar.gz.asc +- SHA512 checksum for binary distribution: apache-fineract-1.6.0-binary.tar.gz.sha512 +- source distribution file: apache-fineract-1.6.0-src.tar.gz +- ASCII armored signature for source distribution: apache-fineract-1.6.0-src.tar.gz.asc +- SHA512 checksum for source distribution: apache-fineract-1.6.0-src.tar.gz.sha512 The signatures are automatically verified by the build script. It will throw an exception if the verification fails. Additionally, you can verify the validity of the release distribution files e. g. with: ``` -gpg --verify build/distributions/apache-fineract-1.4.0-binary.tar.gz.asc +gpg --verify build/distributions/apache-fineract-1.6.0-binary.tar.gz.asc ``` The output should look somewhat like this: ``` -gpg: assuming signed data in 'build/distributions/apache-fineract-1.4.0-binary.tgz' +gpg: assuming signed data in 'build/distributions/apache-fineract-1.6.0-binary.tgz' gpg: Signature made Mi 26 Aug 2020 17:17:45 CEST gpg: using RSA key ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCD gpg: Good signature from "Aleksandar Vidakovic (Apache Fineract Release Manager) " [ultimate] diff --git a/build.gradle b/build.gradle index df1aa1f3f31..72488eaf8c8 100644 --- a/build.gradle +++ b/build.gradle @@ -19,15 +19,17 @@ // TODO: this is work in progress, please follow FINERACT-1171 buildscript { ext { - jacocoVersion = '0.8.6' + jacocoVersion = '0.8.7' retrofitVersion = '2.9.0' - okhttpVersion = '4.9.0' + okhttpVersion = '4.9.2' oltuVersion = '1.0.1' fineractJavaProjects = subprojects.findAll{ [ 'fineract-api', 'fineract-provider', 'integration-tests', + 'twofactor-tests', + 'oauth2-tests', 'fineract-client' ].contains(it.name) } @@ -38,66 +40,76 @@ buildscript { } } repositories { - jcenter() maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath 'com.bmuschko:gradle-cargo-plugin:2.8.0' classpath 'org.apache.openjpa:openjpa:3.2.0' // when upgrading, also change OpenJPA version repeated in fineract-provider/build.gradle! - classpath 'com.radcortez.gradle:openjpa-gradle-plugin:3.1.0' - classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.1' - classpath 'gradle.plugin.com.github.andygoossens:gradle-modernizer-plugin:1.3.0' } } plugins { id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id 'com.diffplug.spotless' version '5.14.1' apply false + id "org.barfuin.gradle.taskinfo" version "1.3.1" + id 'com.adarshr.test-logger' version '3.1.0' + id 'com.diffplug.spotless' version '6.1.0' apply false id 'org.nosphere.apache.rat' version '0.7.0' apply false - id 'com.github.hierynomus.license' version '0.15.0' apply false + id 'com.github.hierynomus.license' version '0.16.1' apply false + id 'com.github.jk1.dependency-license-report' version '2.0' apply false id 'org.openapi.generator' version '4.3.1' apply false - id 'org.zeroturnaround.gradle.jrebel' version '1.1.10' apply false - id 'org.springframework.boot' version '2.3.5.RELEASE' apply false + id 'org.zeroturnaround.gradle.jrebel' version '1.1.11' apply false + id 'org.springframework.boot' version '2.6.3' apply false id 'net.ltgt.errorprone' version '2.0.2' apply false - id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.1.10' apply false - id 'com.gorylenko.gradle-git-properties' version '2.2.4' apply false + id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.1.11' apply false + id 'com.gorylenko.gradle-git-properties' version '2.3.2' apply false id 'org.asciidoctor.jvm.convert' version '3.3.2' apply false id 'org.asciidoctor.jvm.pdf' version '3.3.2' apply false id 'org.asciidoctor.jvm.epub' version '3.3.2' apply false id 'org.asciidoctor.jvm.revealjs' version '3.3.2' apply false id 'org.asciidoctor.jvm.gems' version '3.3.2' apply false id 'org.asciidoctor.kindlegen.base' version '3.2.0' apply false + id "com.google.cloud.tools.jib" version "3.1.4" apply false + id 'fr.brouillard.oss.gradle.jgitver' version '0.10.0-rc03' + id "org.sonarqube" version "3.3" + id "com.github.andygoossens.modernizer" version "1.6.1" apply false + id 'com.github.spotbugs' version '5.0.3' apply false } description = '''\ Run as: gradle clean bootRun''' -project.ext.jerseyVersion = '1.19.4' ext['groovy.version'] = '3.0.6' -ext['swaggerFile'] = "$rootDir/fineract-provider/build/classes/java/main/static/swagger-ui/fineract.yaml".toString() +ext['swaggerFile'] = "$rootDir/fineract-provider/build/generated/swagger-ui/fineract.yaml".toString() allprojects { group = 'org.apache.fineract' + jgitver { + strategy 'PATTERN' + versionPattern '${M}.${m}.${p}-${meta.GIT_SHA1_8}' + } + repositories { - jcenter() + mavenCentral() } apply plugin: 'io.spring.dependency-management' + apply plugin: 'com.adarshr.test-logger' apply plugin: 'com.diffplug.spotless' apply plugin: 'com.github.hierynomus.license' apply plugin: 'org.nosphere.apache.rat' apply plugin: 'project-report' + apply plugin: 'com.github.jk1.dependency-license-report' // Configuration for the dependency management plugin // https://github.com/spring-gradle-plugins/dependency-management-plugin dependencyManagement { imports { - mavenBom 'org.springframework:spring-framework-bom:5.3.8' - mavenBom 'org.springframework.boot:spring-boot-dependencies:2.3.5.RELEASE' - mavenBom 'org.junit:junit-bom:5.7.2' + mavenBom 'org.springframework:spring-framework-bom:5.3.16' + mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.3' + mavenBom 'org.junit:junit-bom:5.8.2' } dependencies { @@ -105,41 +117,38 @@ allprojects { // We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things. // We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available. - dependency 'org.springframework.security.oauth:spring-security-oauth2:2.5.1.RELEASE' dependency 'org.apache.openjpa:openjpa:3.2.0' // when upgrading, also change OpenJPA version repeated above in buildscript! - dependency 'com.google.guava:guava:30.1.1-jre' - dependency 'com.google.code.gson:gson:2.8.7' + dependency 'com.google.guava:guava:31.0.1-jre' + dependency 'com.google.code.gson:gson:2.8.9' dependency 'com.google.truth:truth:1.1.3' dependency 'com.google.truth.extensions:truth-java8-extension:1.1.3' dependency 'org.apache.commons:commons-email:1.5' - dependency 'commons-io:commons-io:2.10.0' - dependency 'org.drizzle.jdbc:drizzle-jdbc:1.4' + dependency 'commons-io:commons-io:2.11.0' dependency 'com.github.librepdf:openpdf:1.3.26' - dependency 'org.mnode.ical4j:ical4j:3.0.28' + dependency 'org.mnode.ical4j:ical4j:3.1.1' dependency 'org.quartz-scheduler:quartz:2.3.2' - dependency 'com.amazonaws:aws-java-sdk-s3:1.12.19' - dependency 'org.ehcache:ehcache:3.9.4' + dependency 'com.amazonaws:aws-java-sdk-s3:1.12.100' + dependency 'org.ehcache:ehcache:3.9.7' dependency 'com.github.spullara.mustache.java:compiler:0.9.10' dependency 'com.jayway.jsonpath:json-path:2.6.0' - dependency 'org.apache.tika:tika-core:1.27' + dependency 'org.apache.tika:tika-core:2.1.0' dependency 'org.apache.httpcomponents:httpclient:4.5.13' - dependency 'io.swagger.core.v3:swagger-annotations:2.1.10' + dependency 'io.swagger.core.v3:swagger-annotations:2.1.11' dependency 'jakarta.management.j2ee:jakarta.management.j2ee-api:1.1.4' dependency 'jakarta.jms:jakarta.jms-api:2.0.3' dependency 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3' - dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.4' - dependency 'jakarta.validation:jakarta.validation-api:3.0.0' - dependency 'org.apache.activemq:activemq-broker:5.16.2' + dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.5' + dependency 'org.apache.activemq:activemq-broker:5.16.3' dependency 'org.apache.bval:org.apache.bval.bundle:2.0.5' - dependency 'io.github.classgraph:classgraph:4.8.110' - dependency 'org.awaitility:awaitility:4.1.0' - dependency 'com.github.spotbugs:spotbugs-annotations:4.3.0' + dependency 'io.github.classgraph:classgraph:4.8.129' + dependency 'org.awaitility:awaitility:4.1.1' + dependency 'com.github.spotbugs:spotbugs-annotations:4.5.2' dependency 'javax.cache:cache-api:1.1.1' dependency 'org.mock-server:mockserver-junit-jupiter:5.11.2' - dependency 'org.webjars.npm:swagger-ui-dist:3.51.1' - dependency 'org.webjars:webjars-locator-core:0.47' - dependency 'org.springframework.boot:spring-boot-starter-mail:2.3.4.RELEASE' + dependency 'org.webjars.npm:swagger-ui-dist:4.0.1' + dependency 'org.webjars:webjars-locator-core:0.48' + dependency 'com.icegreen:greenmail-junit5:1.6.5' // fineract client dependencies dependency "com.squareup.retrofit2:retrofit:$retrofitVersion" @@ -167,13 +176,13 @@ allprojects { dependency "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion" dependency "com.squareup.okhttp3:okhttp-sse:$okhttpVersion" dependency "io.gsonfire:gson-fire:1.8.5" - dependency "io.swagger:swagger-core:1.6.2" - dependency "io.swagger:swagger-annotations:1.6.2" + dependency "io.swagger:swagger-core:1.6.3" + dependency "io.swagger:swagger-annotations:1.6.3" dependency "javax.annotation:javax.annotation-api:1.3.2" dependency "com.google.code.findbugs:jsr305:3.0.2" dependency "commons-codec:commons-codec:1.15" - dependency ('org.flywaydb:flyway-core:7.0.0') { + dependency ('org.flywaydb:flyway-core:8.1.0') { // https://issues.apache.org/jira/browse/FINERACT-1172 // https://github.com/apache/fineract/pull/1355 // https://github.com/flyway/flyway/issues/2957 @@ -188,16 +197,6 @@ allprojects { exclude 'pull-parser:pull-parser' } - dependencySet(group: 'com.sun.jersey', version: jerseyVersion) { - entry 'jersey-core' - entry 'jersey-servlet' - entry 'jersey-server' - entry 'jersey-json' - } - dependencySet(group: 'com.sun.jersey.contribs', version: jerseyVersion) { - entry 'jersey-spring' - entry 'jersey-multipart' - } dependencySet(group: 'org.apache.poi', version: '4.1.2') { entry 'poi' entry 'poi-ooxml' @@ -212,12 +211,20 @@ allprojects { } } + // Configuration for the sonarqube plugin + // https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-gradle/ + sonarqube { + properties { + property "sonar.projectKey", "apache_fineract" + } + } + // Configuration for the spotless plugin // https://github.com/diffplug/spotless/tree/main/plugin-gradle spotless { format 'misc', { target '**/*.md', '**/*.properties', '**/.gitignore', '**/.openapi-generator-ignore', '**/*.yml', '**/*.xml', '**/**.json', '**/*.sql' - targetExclude '**/build/**', '**/bin/**', '**/.settings/**', '**/.idea/**', '**/.gradle/**', '**/gradlew.bat' + targetExclude '**/build/**', '**/bin/**', '**/.settings/**', '**/.idea/**', '**/.gradle/**', '**/gradlew.bat', '**/licenses/**', '**/banner.txt' indentWithSpaces(4) endWithNewline() trimTrailingWhitespace() @@ -248,6 +255,7 @@ allprojects { "**/keystore.jks", "**/static/swagger-ui/**", "**/api-docs/**", + "**/banner.txt", ]) strictCheck true } @@ -261,8 +269,13 @@ allprojects { '**/gradlew*' ]) } + licenseFormat.dependsOn licenseFormatBuildScripts + licenseReport { + outputDir = "$projectDir/licenses" + } + // Configuration for Apache Release Audit Tool task // https://github.com/eskatos/creadur-rat-gradle rat { @@ -355,19 +368,19 @@ allprojects { configure(project.fineractJavaProjects) { // NOTE: order matters! - apply plugin: 'java-library' + apply plugin: 'java' apply plugin: 'idea' apply plugin: 'eclipse' apply plugin: 'checkstyle' apply plugin: 'jacoco' apply plugin: "net.ltgt.errorprone" apply plugin: "com.github.spotbugs" - apply plugin: 'com.github.andygoossens.gradle-modernizer-plugin' + apply plugin: 'com.github.andygoossens.modernizer' /* define the valid syntax level for source files */ - sourceCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 /* define binary compatibility version */ - targetCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_17 /* http://stackoverflow.com/questions/19653311/jpa-repository-works-in-idea-and-production-but-not-in-gradle */ sourceSets.main.output.resourcesDir = sourceSets.main.java.outputDir @@ -404,27 +417,23 @@ configure(project.fineractJavaProjects) { "-Xlint:varargs", "-Xlint:preview", "-Xlint:static", - "-Werror" - ] - // TODO FINERACT-959 (gradually) enable -Xlint:all (see "javac -help -X") - - options.deprecation = true - - options.compilerArgs +=[ + "-Werror", "-Xmaxwarns", 1500, "-Xmaxerrs", 1500 ] + // TODO FINERACT-959 (gradually) enable -Xlint:all (see "javac -help -X") + + options.deprecation = true } - // Configuration for the spotless plugin - // https://github.com/diffplug/spotless/tree/main/plugin-gradle dependencies { spotbugsPlugins 'jp.skypencil.findbugs.slf4j:bug-pattern:1.5.0@jar' - spotbugs 'com.github.spotbugs:spotbugs:4.2.2' } + // Configuration for the spotless plugin + // https://github.com/diffplug/spotless/tree/main/plugin-gradle spotless { java { targetExclude '**/build/**', '**/bin/**', '**/out/**' @@ -466,7 +475,9 @@ configure(project.fineractJavaProjects) { lineEndings 'UNIX' } - compileJava.dependsOn spotlessCheck + // compileJava may create more source files into generated, hence we need to run these tasks after it + licenseMain.dependsOn compileJava + processResources.dependsOn compileJava // If we are running Gradle within Eclipse to enhance classes with OpenJPA, // set the classes directory to point to Eclipse's default build directory @@ -477,7 +488,7 @@ configure(project.fineractJavaProjects) { // Configuration for the Checkstyle plugin // https://docs.gradle.org/current/userguide/checkstyle_plugin.html dependencies { - checkstyle 'com.puppycrawl.tools:checkstyle:8.44' + checkstyle 'com.puppycrawl.tools:checkstyle:9.1' checkstyle 'com.github.sevntu-checkstyle:sevntu-checks:1.40.0' } @@ -499,7 +510,7 @@ configure(project.fineractJavaProjects) { // Configuration for the errorprone plugin // https://github.com/tbroyer/gradle-errorprone-plugin dependencies { - errorprone "com.google.errorprone:error_prone_core:2.6.0" + errorprone "com.google.errorprone:error_prone_core:2.10.0" } tasks.withType(JavaCompile) { @@ -517,6 +528,7 @@ configure(project.fineractJavaProjects) { "AlmostJavadoc", "InvalidBlockTag", "JavaUtilDate", // TODO FINERACT-1298 + "ReturnValueIgnored" ) error( "DefaultCharset", @@ -603,12 +615,10 @@ configure(project.fineractJavaProjects) { test { useJUnitPlatform() - testLogging { - // FINERACT-927 - events "skipped", "failed" - showStandardStreams = false - exceptionFormat "full" - } + } + + testlogger { + logLevel 'quiet' } // Configuration for spotbugs plugin @@ -617,7 +627,7 @@ configure(project.fineractJavaProjects) { // To generate an HTML report instead of XML spotbugs { reportLevel = 'high' - showProgress = true + showProgress = false } // https://github.com/spotbugs/spotbugs-gradle-plugin/issues/242 spotbugsMain { @@ -648,7 +658,6 @@ configure(project.fineractJavaProjects) { } configure(project.fineractPublishProjects) { - apply plugin: 'maven' apply plugin: 'maven-publish' publishing { @@ -687,3 +696,18 @@ configure(project.fineractPublishProjects) { // TODO FINERACT-1102: Actually use this to deploy to ASF (Apache Software Foundation) Nexus Maven Repository } } + +task printSourceSetInformation() { + doLast{ + sourceSets.each { srcSet -> + println "["+srcSet.name+"]" + print "-->Source directories: "+srcSet.allJava.srcDirs+"\n" + print "-->Output directories: "+srcSet.output.classesDirs.files+"\n" + print "-->Compile classpath:\n" + srcSet.compileClasspath.files.each { + print " "+it.path+"\n" + } + println "" + } + } +} diff --git a/config/fineractdev-formatter.xml b/config/fineractdev-formatter.xml index 68b50c4b31b..bedc59ca233 100644 --- a/config/fineractdev-formatter.xml +++ b/config/fineractdev-formatter.xml @@ -100,7 +100,7 @@ - + diff --git a/docker-compose.yml b/docker-compose.yml index 3724c3404e5..c38e6701745 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,8 +22,9 @@ version: '3.7' services: # Backend service fineractmysql: - image: mysql:5.7 + image: mariadb:10.6 volumes: + - ./fineract-db/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf - ./fineract-db/docker:/docker-entrypoint-initdb.d:Z,ro restart: always environment: @@ -35,26 +36,60 @@ services: ports: - "3306:3306" fineract-server: - build: - context: . - target: fineract + image: fineract:latest + volumes: + - ./fineract-provider/build/data:/data + healthcheck: + test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "fineract-server" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ] + timeout: 10s + retries: 10 ports: - 8443:8443 depends_on: - - fineractmysql + fineractmysql: + condition: service_healthy environment: - - DRIVERCLASS_NAME=org.drizzle.jdbc.DrizzleDriver - - PROTOCOL=jdbc - - SUB_PROTOCOL=mysql:thin - - fineract_tenants_driver=org.drizzle.jdbc.DrizzleDriver - - fineract_tenants_url=jdbc:mysql:thin://fineractmysql:3306/fineract_tenants - - fineract_tenants_uid=root - - fineract_tenants_pwd=skdcnwauicn2ucnaecasdsajdnizucawencascdca + # TODO: env vars prefixed with "fineract_tenants_*" will be removed with one of the next releases + #- fineract_tenants_driver=org.mariadb.jdbc.Driver + #- fineract_tenants_url=jdbc:mariadb://fineractmysql:3306/fineract_tenants + #- fineract_tenants_uid=root + #- fineract_tenants_pwd=skdcnwauicn2ucnaecasdsajdnizucawencascdca + # NOTE: node aware scheduler + - FINERACT_NODE_ID=1 + # NOTE: env vars prefixed "FINERACT_HIKARI_*" are used to configure the database connection pool + - FINERACT_HIKARI_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver + - FINERACT_HIKARI_JDBC_URL=jdbc:mariadb://fineractmysql:3306/fineract_tenants + - FINERACT_HIKARI_USERNAME=root + - FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca + # ... following variables are optional; "application.properties" contains reasonable defaults (same as here) + - FINERACT_HIKARI_MINIMUM_IDLE=3 + - FINERACT_HIKARI_MAXIMUM_POOL_SIZE=10 + - FINERACT_HIKARI_IDLE_TIMEOUT=60000 + - FINERACT_HIKARI_CONNECTION_TIMEOUT=20000 + - FINERACT_HIKARI_TEST_QUERY=SELECT 1 + - FINERACT_HIKARI_AUTO_COMMIT=true + - FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS=true + - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE=250 + - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT=2048 + - FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS=true + - FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE=true + - FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS=true + - FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA=true + - FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION=true + - FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS=true + - FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS=false + - FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES=true + - FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION=true + # NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to create the default tenant database - FINERACT_DEFAULT_TENANTDB_HOSTNAME=fineractmysql - FINERACT_DEFAULT_TENANTDB_PORT=3306 - FINERACT_DEFAULT_TENANTDB_UID=root - FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca - FINERACT_DEFAULT_TENANTDB_CONN_PARAMS= + - FINERACT_DEFAULT_TENANTDB_TIMEZONE=Asia/Kolkata + - FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default + - FINERACT_DEFAULT_TENANTDB_NAME=fineract_default + - FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant # Frontend service community-app: @@ -63,10 +98,6 @@ services: restart: always ports: - 9090:80 - - -# https://issues.apache.org/jira/browse/FINERACT-762 -# To use an altnerative JDBC driver (which is faster, but not allowed to be default in Apache distribution) -# replace org.drizzle.jdbc.DrizzleDriver with com.mysql.jdbc.Driver in DRIVERCLASS_NAME and fineract_tenants_driver, -# and remove ":thin:" from SUB_PROTOCOL and fineract_tenants_url. Note that the mysql-connector-java-*.jar is already -# bundled in the container by the Dockerfile, but just not used by default. + depends_on: + fineract-server: + condition: service_healthy diff --git a/fineract-client/README.md b/fineract-client/README.md new file mode 100644 index 00000000000..eed7fcbf0ac --- /dev/null +++ b/fineract-client/README.md @@ -0,0 +1,7 @@ +This is the source of the official Apache Fineract Java Client library. + +It is mostly autogenerated using **Swagger Codegen**, but includes a number of hand-written utility classes to aid in its usage. + +There is some [documentation about it here](https://github.com/apache/fineract/blob/develop/fineract-doc/src/docs/en/05_client.adoc). + +Please contribute improvements to this (a submodule of Apache Fineract) if this client library has gaps for what you need it for. diff --git a/fineract-client/build.gradle b/fineract-client/build.gradle index 4bcbe9e3004..d20c2a5af01 100644 --- a/fineract-client/build.gradle +++ b/fineract-client/build.gradle @@ -31,7 +31,7 @@ openApiMeta { } openApiValidate { - inputSpec = "$swaggerFile" + inputSpec = "file:///$swaggerFile" recommend = true } @@ -40,8 +40,9 @@ task buildJavaSdk(type: org.openapitools.generator.gradle.plugin.tasks.GenerateT verbose = false validateSpec = false skipValidateSpec = true - inputSpec = "$swaggerFile" + inputSpec = "file:///$swaggerFile" outputDir = "$buildDir/generated/java".toString() + templateDir = "$rootDir/fineract-client/src/main/resources/java/template/" groupId = 'org.apache.fineract' apiPackage = 'org.apache.fineract.client.services' invokerPackage = 'org.apache.fineract.client' @@ -57,9 +58,14 @@ task buildJavaSdk(type: org.openapitools.generator.gradle.plugin.tasks.GenerateT ignoreFileOverride = "$projectDir/.openapi-generator-ignore" // trick to make sure fineract.yaml is generated first dependsOn = [ - ':fineract-provider:compileJava' + ':fineract-provider:resolve' ] finalizedBy = [licenseFormat] + // uncomment below block to automatically copy the custom files in generated SDK + /*copy { + from file('src/main/java') + into file("$buildDir/generated/java/src/main/java") + }*/ } task buildTypescriptAngularSdk(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask){ @@ -67,7 +73,7 @@ task buildTypescriptAngularSdk(type: org.openapitools.generator.gradle.plugin.ta verbose = false validateSpec = false skipValidateSpec = true - inputSpec = "$swaggerFile" + inputSpec = "file:///$swaggerFile" outputDir = "$buildDir/generated/typescript".toString() apiPackage = 'org.apache.fineract.client.services' invokerPackage = 'org.apache.fineract.client' @@ -78,6 +84,9 @@ task buildTypescriptAngularSdk(type: org.openapitools.generator.gradle.plugin.ta ngVersion: '10.0.0', npmName: 'apache-fineract-client' ] + dependsOn = [ + ':fineract-provider:resolve' + ] finalizedBy = [licenseFormat] } @@ -93,7 +102,7 @@ sourceSets { } java { - // keep this at Java 8, not 11; see https://issues.apache.org/jira/browse/FINERACT-1214 + // keep this at Java 8, not 17; see https://issues.apache.org/jira/browse/FINERACT-1214 sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } @@ -112,10 +121,13 @@ tasks.withType(JavaCompile) { } configurations { - generatedCompile.extendsFrom implementation - generatedRuntime.extendsFrom runtime + generatedCompileClasspath.extendsFrom implementation + generatedRuntimeClasspath.extendsFrom runtimeClasspath } test { useJUnitPlatform() } + +// Gradle 7.x asks for explicit dependencies between tasks +licenseFormatBuildScripts.dependsOn spotlessGroovyGradle, spotlessJava, spotlessMisc, compileJava, processResources, compileGeneratedJava, compileTestJava, rat, test diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index 3aff2d2a080..a8cce6540a2 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -106,6 +106,7 @@ import org.apache.fineract.client.services.RolesApi; import org.apache.fineract.client.services.RunReportsApi; import org.apache.fineract.client.services.SavingsAccountApi; +import org.apache.fineract.client.services.SavingsAccountTransactionsApi; import org.apache.fineract.client.services.SavingsChargesApi; import org.apache.fineract.client.services.SavingsProductApi; import org.apache.fineract.client.services.SchedulerApi; @@ -235,6 +236,7 @@ public final class FineractClient { public final SavingsAccountApi savingsAccounts; public final SavingsChargesApi savingsAccountCharges; public final SavingsProductApi savingsProducts; + public final SavingsAccountTransactionsApi savingsTransactions; public final SchedulerApi jobsScheduler; public final ScoreCardApi surveyScorecards; public final SearchApiApi search; @@ -344,6 +346,7 @@ private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) { savingsAccounts = retrofit.create(SavingsAccountApi.class); savingsAccountCharges = retrofit.create(SavingsChargesApi.class); savingsProducts = retrofit.create(SavingsProductApi.class); + savingsTransactions = retrofit.create(SavingsAccountTransactionsApi.class); jobsScheduler = retrofit.create(SchedulerApi.class); surveyScorecards = retrofit.create(ScoreCardApi.class); search = retrofit.create(SearchApiApi.class); diff --git a/fineract-client/src/main/resources/java/template/build.gradle.mustache b/fineract-client/src/main/resources/java/template/build.gradle.mustache new file mode 100644 index 00000000000..e619732674a --- /dev/null +++ b/fineract-client/src/main/resources/java/template/build.gradle.mustache @@ -0,0 +1,218 @@ +{{! +/** + * 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. + */ +}} +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { +repositories { +maven { url "https://repo1.maven.org/maven2" } +jcenter() +} +dependencies { +classpath 'com.android.tools.build:gradle:2.3.+' +classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' +} +} + +repositories { +jcenter() +} + + +if(hasProperty('target') && target == 'android') { + +apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' + +android { +compileSdkVersion 25 +buildToolsVersion '25.0.2' +defaultConfig { +minSdkVersion 14 +targetSdkVersion 25 +} +compileOptions { +{{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 +{{/java8}} +{{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 +{{/java8}} +} + +// Rename the aar correctly +libraryVariants.all { variant -> +variant.outputs.each { output -> +def outputFile = output.outputFile +if (outputFile != null && outputFile.name.endsWith('.aar')) { +def fileName = "${project.name}-${variant.baseName}-${version}.aar" +output.outputFile = new File(outputFile.parent, fileName) +} +} +} + +dependencies { +provided 'javax.annotation:jsr250-api:1.0' +} +} + +afterEvaluate { +android.libraryVariants.all { variant -> +def task = project.tasks.create "jar${variant.name.capitalize()}", Jar +task.description = "Create jar artifact for ${variant.name}" +task.dependsOn variant.javaCompile +task.from variant.javaCompile.destinationDir +task.destinationDir = project.file("${project.buildDir}/outputs/jar") +task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" +artifacts.add('archives', task); +} +} + +task sourcesJar(type: Jar) { +from android.sourceSets.main.java.srcDirs +classifier = 'sources' +} + +artifacts { +archives sourcesJar +} + +} else { + +apply plugin: 'java' +apply plugin: 'maven' + +{{#java8}} + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +{{/java8}} +{{^java8}} + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 +{{/java8}} + +install { +repositories.mavenInstaller { +pom.artifactId = '{{artifactId}}' +} +} + +task execute(type:JavaExec) { +main = System.getProperty('mainClass') +classpath = sourceSets.main.runtimeClasspath +} +} + +ext { +oltu_version = "1.0.1" +retrofit_version = "2.9.0" +okhttp3_version = "4.9.0" +{{#usePlayWS}} + jackson_version = "2.10.5" + jackson_databind_version = "2.10.5.1" + {{#openApiNullable}} + jackson_databind_nullable_version = "0.2.1" + {{/openApiNullable}} + {{#play24}} + play_version = "2.4.11" + {{/play24}} + {{#play25}} + play_version = "2.5.14" + {{/play25}} + {{#play26}} + play_version = "2.6.7" + {{/play26}} +{{/usePlayWS}} +swagger_annotations_version = "1.5.22" +junit_version = "4.13.1" +{{#useRxJava}} + rx_java_version = "1.3.0" +{{/useRxJava}} +{{#useRxJava2}} + rx_java_version = "2.1.1" +{{/useRxJava2}} +{{#useRxJava3}} + rx_java_version = "3.0.4" +{{/useRxJava3}} +{{#joda}} + jodatime_version = "2.9.9" +{{/joda}} +{{#threetenbp}} + threetenbp_version = "1.4.0" +{{/threetenbp}} +json_fire_version = "1.8.0" +} + +dependencies { +implementation "com.squareup.retrofit2:retrofit:$retrofit_version" +implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version" +implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" +implementation "com.squareup.okhttp3:logging-interceptor:4.9.0" +implementation "com.squareup.okhttp3:okhttp:$okhttp3_version" +{{#useRxJava}} + implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" + implementation "io.reactivex:rxjava:$rx_java_version" +{{/useRxJava}} +{{#useRxJava2}} + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + implementation "io.reactivex.rxjava2:rxjava:$rx_java_version" +{{/useRxJava2}} +{{#useRxJava3}} + implementation 'com.github.akarnokd:rxjava3-retrofit-adapter:3.0.0' + implementation "io.reactivex.rxjava3:rxjava:$rx_java_version" +{{/useRxJava3}} +implementation "io.swagger:swagger-annotations:$swagger_annotations_version" +implementation "com.google.code.findbugs:jsr305:3.0.2" +implementation ("org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltu_version"){ +exclude group:'org.apache.oltu.oauth2' , module: 'org.apache.oltu.oauth2.common' +} +implementation "io.gsonfire:gson-fire:$json_fire_version" +{{#joda}} + implementation "joda-time:joda-time:$jodatime_version" +{{/joda}} +{{#threetenbp}} + implementation "org.threeten:threetenbp:$threetenbp_version" +{{/threetenbp}} +{{#usePlayWS}} + {{#play26}} + implementation "com.typesafe.play:play-ahc-ws_2.12:$play_version" + implementation "javax.validation:validation-api:1.1.0.Final" + {{/play26}} + {{^play26}} + implementation "com.typesafe.play:play-java-ws_2.11:$play_version" + {{/play26}} + implementation "com.squareup.retrofit2:converter-jackson:$retrofit_version" + implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version" + {{#openApiNullable}} + implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version" + {{/openApiNullable}} + implementation "com.fasterxml.jackson.datatype:jackson-datatype-{{^java8}}joda{{/java8}}{{#java8}}jsr310{{/java8}}:$jackson_version" +{{/usePlayWS}} +implementation 'javax.annotation:javax.annotation-api:1.3.2' +testImplementation "junit:junit:$junit_version" +} \ No newline at end of file diff --git a/fineract-client/src/main/resources/java/template/pom.mustache b/fineract-client/src/main/resources/java/template/pom.mustache new file mode 100644 index 00000000000..f2aa1939ff9 --- /dev/null +++ b/fineract-client/src/main/resources/java/template/pom.mustache @@ -0,0 +1,479 @@ +{{! +/** + * 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. + */ +}} + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + {{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + + {{/parentOverridden}} + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M1 + + + enforce-maven + + enforce + + + + + 2.2.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + {{#java8}} + 1.8 + 1.8 + {{/java8}} + {{^java8}} + 1.7 + 1.7 + {{/java8}} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.1 + + none + {{#java8}} + 1.8 + {{/java8}} + {{^java8}} + 1.7 + {{/java8}} + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + + + com.squareup.okhttp3 + okhttp + ${okhttp3-version} + + + + com.squareup.okhttp3 + logging-interceptor + 3.12.1 + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + com.squareup.retrofit2 + converter-gson + ${retrofit-version} + + + com.squareup.retrofit2 + retrofit + ${retrofit-version} + + + com.squareup.retrofit2 + converter-scalars + ${retrofit-version} + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + ${oltu-version} + + + org.apache.oltu.oauth2 + common + + + + + io.gsonfire + gson-fire + ${gson-fire-version} + + {{#joda}} + + joda-time + joda-time + ${jodatime-version} + + {{/joda}} + {{#threetenbp}} + + org.threeten + threetenbp + ${threetenbp-version} + + {{/threetenbp}} + {{#useRxJava}} + + io.reactivex + rxjava + ${rxjava-version} + + + com.squareup.retrofit2 + adapter-rxjava + ${retrofit-version} + + {{/useRxJava}} + {{#useRxJava2}} + + io.reactivex.rxjava2 + rxjava + ${rxjava-version} + + + com.squareup.retrofit2 + adapter-rxjava2 + ${retrofit-version} + + {{/useRxJava2}} + {{#useRxJava3}} + + io.reactivex.rxjava3 + rxjava + ${rxjava-version} + + + com.github.akarnokd + rxjava3-retrofit-adapter + 3.0.0 + + {{/useRxJava3}} + {{#usePlayWS}} + + + com.squareup.retrofit2 + converter-jackson + ${retrofit-version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + {{#openApiNullable}} + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable-version} + + {{/openApiNullable}} + + com.fasterxml.jackson.datatype + jackson-datatype-{{^java8}}joda{{/java8}}{{#java8}}jsr310{{/java8}} + ${jackson-version} + + {{#withXml}} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + {{/withXml}} + {{#play24}} + + com.typesafe.play + play-java-ws_2.11 + ${play-version} + + {{/play24}} + {{#play25}} + + com.typesafe.play + play-java-ws_2.11 + ${play-version} + + {{/play25}} + {{#play26}} + + com.typesafe.play + play-ahc-ws_2.12 + ${play-version} + + + javax.validation + validation-api + 1.1.0.Final + + {{/play26}} + {{/usePlayWS}} + {{#parcelableModel}} + + + com.google.android + android + 4.1.1.4 + provided + + {{/parcelableModel}} + + javax.annotation + javax.annotation-api + ${javax-annotation-version} + provided + + + + junit + junit + ${junit-version} + test + + + + UTF-8 + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} + ${java.version} + ${java.version} + 1.8.3 + 1.5.22 + {{#usePlayWS}} + 2.12.1 + {{#play24}} + 2.4.11 + {{/play24}} + {{#play25}} + 2.5.15 + {{/play25}} + {{#play26}} + 2.6.7 + {{/play26}} + {{#openApiNullable}} + 0.2.1 + {{/openApiNullable}} + {{/usePlayWS}} + 2.9.0 + 4.9.0 + {{#useRxJava}} + 1.3.0 + {{/useRxJava}} + {{#useRxJava2}} + 2.1.1 + {{/useRxJava2}} + {{#useRxJava3}} + 3.0.4 + {{/useRxJava3}} + {{#joda}} + 2.9.9 + {{/joda}} + {{#threetenbp}} + 1.4.0 + {{/threetenbp}} + 1.3.2 + 1.0.1 + 4.13.1 + + \ No newline at end of file diff --git a/fineract-provider/properties/basicauth/twofactor/application.properties b/fineract-db/server_collation.cnf similarity index 78% rename from fineract-provider/properties/basicauth/twofactor/application.properties rename to fineract-db/server_collation.cnf index a9b13cc5e4e..2b3df519b16 100644 --- a/fineract-provider/properties/basicauth/twofactor/application.properties +++ b/fineract-db/server_collation.cnf @@ -1,4 +1,3 @@ -# # 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 @@ -17,12 +16,13 @@ # under the License. # -spring.profiles.default=basicauth -spring.profiles.active=basicauth,twofactor -management.health.jms.enabled=false +[client] +default-character-set=utf8mb4 -# FINERACT-883 -management.info.git.mode=FULL +[mysql] +default-character-set=utf8mb4 -# FINERACT-914 -server.forward-headers-strategy=framework +[mysqld] +collation-server=utf8mb4_unicode_ci +init-connect='SET NAMES utf8mb4' +character-set-server=utf8mb4 diff --git a/fineract-doc/build.gradle b/fineract-doc/build.gradle index 78c12727eab..e216001da79 100644 --- a/fineract-doc/build.gradle +++ b/fineract-doc/build.gradle @@ -51,7 +51,7 @@ asciidoctorj { imagesdir: 'images', diagrams: 'diagrams', years: '2015-2020', - revnumber: "$releaseVersion".toString(), + revnumber: "$version".toString(), rootdir: "$rootDir".toString(), baseurl: 'fineract.apache.org' ] diff --git a/fineract-doc/src/docs/en/02_architecture.adoc b/fineract-doc/src/docs/en/02_architecture.adoc index 837cbb404cb..6bb82bcfa5e 100644 --- a/fineract-doc/src/docs/en/02_architecture.adoc +++ b/fineract-doc/src/docs/en/02_architecture.adoc @@ -10,7 +10,7 @@ The target audience for this report is both system integrators (who will use the ==== The Idea -Mifos was an idea born out of a wish to create and deploy technology that allows the microfinance industry to scale. The goal is to: +Fineract was an idea born out of a wish to create and deploy technology that allows the microfinance industry to scale. The goal is to: - Produce a gold standard management information system suitable for microfinance operations - Acts as the basis of a platform for microfinance @@ -23,12 +23,13 @@ Mifos was an idea born out of a wish to create and deploy technology that allows - Late 2011: Grameen Foundation handed over full responsibility to open source community. - 2012: Mifos X platform started. Previous members of project come together under the name of Community for Open Source Microfinance (COSM / OpenMF) - 2013: COSM / OpenMF officially rebranded to Mifos Initiative and receive US 501c3 status. +- 2016: Fineract 1.x began incubation at Apache ==== Project Related -- Project url is https://github.com/openMF/mifosx -- Download from https://sourceforge.net/projects/mifos/ -- Download stats +- Project url is https://github.com/apache/fineract +- Issue tracker is https://issues.apache.org/jira/projects/FINERACT/summary +- Download from http://fineract.apache.org/ === System Overview @@ -91,8 +92,8 @@ At a higher level though we see the capabilities fall into the following categor === Technology -* Java 11: http://www.oracle.com/technetwork/java/javase/downloads/index.html -* JAX-RS 1.0: using Jersey (1.17.x) +* Java: http://www.oracle.com/technetwork/java/javase/downloads/index.html +* JAX-RS using Jersey * JSON using Google GSON * Spring I/O Platform: http://spring.io/platform ** Spring Framework @@ -116,7 +117,7 @@ See online API Documentation for more detail. ==== Multi-tenanted -The mifos platform has been developed with support for multi-tenancy at the core of its design. This means that it is just as easy to use the platform for Software-as-a-Service (SaaS) type offerings as it is for local installations. +The Fineract platform has been developed with support for multi-tenancy at the core of its design. This means that it is just as easy to use the platform for Software-as-a-Service (SaaS) type offerings as it is for local installations. The platform uses an approach that isolates an FIs data per database/schema (See Separate Databases and Shared Database, Separate Schemas). diff --git a/fineract-doc/src/docs/en/03_oauth.adoc b/fineract-doc/src/docs/en/03_oauth.adoc index fec07da8b56..933b719a9a4 100644 --- a/fineract-doc/src/docs/en/03_oauth.adoc +++ b/fineract-doc/src/docs/en/03_oauth.adoc @@ -1,41 +1,66 @@ == OAuth -Fineract has (basic) OAuth (2.0?) support. Here's how to use it: +Fineract has a (basic) OAuth2 support based on Spring Boot Security. Here's how to use it: === Build You must re-build the distribution JAR (or WAR) using the special `-Psecurity=oauth` flag: ---- -./gradlew bootJAR -Psecurity=oauth -java -jar build/libs/fineract-provider.jar +./gradlew bootRun -Psecurity=oauth ---- Downloads from https://fineract.apache.org, or using e.g. the https://hub.docker.com/r/apache/fineract container image, or on https://www.fineract.dev, this will not work, because they have not been built using this flag. -=== Invoke `/fineract-provider/api/oauth/token` +Previous versions of Fineract included a built-in authorisation server for issuing OAuth tokens. However, as the spring-security-oauth2 package was deprecated and replaced by built-in OAuth support in Spring Security, this is no longer supported as part of the package. Instead, you need to run a separate OAuth authorization server (e.g. https://github.com/spring-projects/spring-authorization-server) or use a 3rd-party OAuth authorization provider (https://en.wikipedia.org/wiki/List_of_OAuth_providers) + +This instruction describes how to get Fineract OAuth working with a Keycloak (http://keycloak.org) based authentication provider running in a Docker container. The steps required for other OAuth providers will be similar. + +=== Set up Keycloak + +1. From terminal, run: 'docker run -p 9000:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.0.2' +1. Go to URL 'http://localhost:9000/auth/admin' and login with admin/admin +1. Hover your mouse over text "Master" and click on "Add realm" +1. Enter name "fineract" for your realm +1. Click on tab "Users" on the left, then "Add user" and create user with username "mifos" +1. Click on tab "Credentials" at the top, and set password to "password", turning "temporary" setting to off +1. Click on tab "Clients" on the left, and create client with ID 'community-app' +1. In settings tab, set 'access-type' to 'confidential' and enter 'localhost' in the valid redirect URIs. +1. In credentials tab, copy string in field 'secret' as this will be needed in the step to request the access token + +Finally we need to change Keycloak configuration so that it uses the username as a subject of the token: + +1. Choose client 'community-app' in the tab 'Clients' +1. Go to tab 'Mappers' and click on 'Create' +1. Enter 'usernameInSub' as 'Name' +1. Choose mapper type 'User Property' +1. Enter 'username' into the field 'Property' and 'sub' into the field 'Token Claim Name'. Choose 'String' as 'Claim JSON Type' + +You are now ready to test out OAuth: + +=== Retrieve an access token from Keycloak ---- -curl [--insecure] --location --request POST \ -'https://localhost:8443/fineract-provider/api/oauth/token' \ ---header 'Fineract-Platform-TenantId: default' \ +curl --location --request POST \ +'http://localhost:9000/auth/realms/fineract/protocol/openid-connect/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'username=mifos' \ --data-urlencode 'password=password' \ --data-urlencode 'client_id=community-app' \ --data-urlencode 'grant_type=password' \ ---data-urlencode 'client_secret=123' +--data-urlencode 'client_secret=' ---- -Note that the `client_id` and `client_secret` are stored in the `oauth_client_details` table in the database. +The reply should contain a field 'access_token'. Copy the field's value and use it in the API call below: === Invoke APIs and pass `Authorization: bearer ...` header ---- curl --location --request GET \ -'https://localhost:8443/fineract-provider/api/v1/clients' \ +'https://localhost:8443/fineract-provider/api/v1/offices' \ --header 'Fineract-Platform-TenantId: default' \ ---header 'Authorization: bearer RzfUyQ0wEnxxq4PyFCF1J-XGFCI' +--header 'Authorization: bearer ' + ---- -NOTE: See also https://demo.fineract.dev/fineract-provider/api-docs/apiLive.htm#authentication_oauth \ No newline at end of file +NOTE: See also https://demo.fineract.dev/fineract-provider/api-docs/apiLive.htm#authentication_oauth diff --git a/fineract-doc/src/docs/en/05_client.adoc b/fineract-doc/src/docs/en/05_client.adoc index 6b8c06ffd82..917ca06a78b 100644 --- a/fineract-doc/src/docs/en/05_client.adoc +++ b/fineract-doc/src/docs/en/05_client.adoc @@ -6,7 +6,7 @@ Apache Fineract supports client code generation using https://openapi-generator. The `fineract-client.jar` will eventually be available on Maven Central (watch https://issues.apache.org/jira/browse/FINERACT-1102[FINERACT-1102]). Until it is, you can quite easily build the latest and greatest version locally from source, see below. -The https://github.com/apache/fineract/search?q=FineractClient.java[`FineractClient`] is the entry point to the _Fineract SDK Java API Client_. https://github.com/apache/fineract/search?q=Calls.java[`Calls`] is a convenient and recommended utility to simplify the use of the https://square.github.io/retrofit/2.x/retrofit/retrofit2/Call.html[`retrofit2.Call`] type which all API operations return. This permits you to use the API like the https://github.com/apache/fineract/search?q=FineractClientDemo.java[`FineractClientDemo`] illustrates: +The https://github.com/apache/fineract/search?q=FineractClient.java[`FineractClient`] is the entry point to the _Fineract SDK Java API Client_. https://github.com/apache/fineract/search?q=Calls.java[`Calls`] is a convenient and recommended utility to simplify the use of the https://square.github.io/retrofit/2.x/retrofit/retrofit2/Call.html[`retrofit2.Call`] type which all API operations return. This permits you to use the API like the https://github.com/search?l=&q=repo%3Aapache%2Ffineract+filename%3AFineractClientDemo.java&type=code[`FineractClientDemo`] illustrates: [source,java] ---- diff --git a/fineract-doc/src/docs/en/diagrams/samples/amazon.puml b/fineract-doc/src/docs/en/diagrams/samples/amazon.puml deleted file mode 100644 index 2202e77a813..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/amazon.puml +++ /dev/null @@ -1,47 +0,0 @@ -@startuml -!include - -' Uncomment the following line to create simplified view -!include - -!include -!include -!include -!include -!include -!include -!include -!include -!include -!include - -left to right direction - -agent "Published Event" as event #fff - -IoTRule(iotRule, "Action Error Rule", "error if Kinesis fails") -KinesisDataStreams(eventStream, "IoT Events", "2 shards") -SQSQueue(errorQueue, "Rule Error Queue", "failed Rule actions") - -event --> iotRule : JSON message -iotRule --> eventStream : messages -iotRule --> errorQueue : Failed action message - -Users(sources, "Events", "millions of users") -APIGateway(votingAPI, "Voting API", "user votes") -Cognito(userAuth, "User Authentication", "jwt to submit votes") -Lambda(generateToken, "User Credentials", "return jwt") -Lambda(recordVote, "Record Vote", "enter or update vote per user") -DynamoDB(voteDb, "Vote Database", "one entry per user") - -SimpleStorageServiceS3(s3_partner, "Vendor's S3", "Technology", "Optional Description") -SimpleStorageServiceS3(s3_internal, "Internal", "Technology", "Optional Description") - -s3_internal <- s3_partner - -sources --> userAuth -sources --> votingAPI -userAuth <--> generateToken -votingAPI --> recordVote -recordVote --> voteDb -@enduml diff --git a/fineract-doc/src/docs/en/diagrams/samples/archimate.puml b/fineract-doc/src/docs/en/diagrams/samples/archimate.puml deleted file mode 100644 index 6a9d0120162..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/archimate.puml +++ /dev/null @@ -1,64 +0,0 @@ -@startuml -!includeurl https://raw.githubusercontent.com/ebbypeter/Archimate-PlantUML/master/Archimate.puml -'!include - -title Archimate Example - Relationships - -'LAYOUT_AS_SKETCH -'LAYOUT_LEFT_RIGHT -'LAYOUT_TOP_DOWN - -Business_Service(Service01, "Service 01") -Application_Service(Service02, "Service 02") -Technology_Service(Service03, "Service03") - -Rel_Composition_Right(Service01, Service02, "Composition") -Rel_Aggregation(Service01, Service02, "Aggregation") -Rel_Assignment(Service01, Service02, "Assignment") -Rel_Specialization(Service01, Service02, "Specialization") -Rel_Serving(Service01, Service02, "Serving") -Rel_Association(Service02, Service03, "Association") -Rel_Flow(Service02, Service03, "Flow") -Rel_Realization(Service02, Service03, "Realization") -Rel_Triggering(Service02, Service03, "Triggering") -Rel_Access(Service02, Service03, "Access") -Rel_Influence(Service02, Service03, " + Influence") - -Grouping(Example01, "Example 01"){ - Strategy_Capability(TestCapability01, "Test Capability") - Business_Product(TestProduct01, "Test Product") - Application_Service(TestService01, "Test Service") - Technology_Device(TestDevice01, "Test Device") - - Rel_Composition(TestCapability01, TestProduct01, "C-P") - Rel_Composition_Down(TestProduct01, TestService01, "P-S") - Rel_Composition_Left(TestService01, TestDevice01, "S-D") - Rel_Composition_Up(TestDevice01, TestCapability01, "D-C") -} - -Lay_D(Service01, Example01) - -Group(Example02, "Group Type 02"){ - Motivation_Stakeholder(StakeholderElement, "Stakeholder Description") - Business_Service(BService, "Business Service") -} - -Motivation_Requirement(ReqPayrollStandard, "Do Payroll with a standard system") -Motivation_Requirement(ReqBudgetPlanning, "Do budget planning within the ERP system") - -Application_Service(ASPayroll,"Payroll Service") -Application_Service(ASBudgetPlanning,"Budget Planning Service") -Application_Component(ACSAPFinanceAccRec, "SAP Finance - Accounts Recievables") -Application_Component(ACSAPHR, "SAP Human Resources") -Application_Component(ACSAPFin, "SAP Finance") -Application_Component(ACSAP,"SAP") - -Rel_Realization_Up(ASPayroll, ReqPayrollStandard) -Rel_Realization_Up(ASBudgetPlanning, ReqBudgetPlanning) -Rel_Realization_Up(ACSAPFinanceAccRec, ASBudgetPlanning) -Rel_Realization_Up(ACSAPHR, ASPayroll) - -Rel_Composition_Up(ACSAPFin, ACSAPFinanceAccRec) -Rel_Composition_Up(ACSAP, ACSAPHR) -Rel_Composition_Up(ACSAP, ACSAPFin) -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/c4.puml b/fineract-doc/src/docs/en/diagrams/samples/c4.puml deleted file mode 100644 index db9ec6f17d8..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/c4.puml +++ /dev/null @@ -1,49 +0,0 @@ -@startuml -' see: https://github.com/RicardoNiepel/C4-PlantUML -'!includeurl https://raw.githubusercontent.com/RicardoNiepel/C4-PlantUML/master/C4_Container.puml -!include - -LAYOUT_TOP_DOWN -' LAYOUT_WITH_LEGEND - -Person_Ext(anonymous_user, "Anonymous User") -Person(aggregated_user, "Aggregated User") -Person(administration_user, "Administration User") - -System_Boundary(c1, "techtribes.js"){ - - Container(web_app, "Web Application", "Java, Spring MVC, Tomcat 7.x", "Allows users to view people, tribes, content, events, jobs, etc. from the local tech, digital and IT sector") - - ContainerDb(rel_db, "Relational Database", "MySQL 5.5.x", "Stores people, tribes, tribe membership, talks, events, jobs, badges, GitHub repos, etc.") - - Container(filesystem, "File System", "FAT32", "Stores search indexes") - - ContainerDb(nosql, "NoSQL Data Store", "MongoDB 2.2.x", "Stores from RSS/Atom feeds (blog posts) and tweets") - - Container(updater, "Updater", "Java 7 Console App", "Updates profiles, tweets, GitHub repos and content on a scheduled basis") -} - -System_Ext(twitter, "Twitter") -System_Ext(github, "GitHub") -System_Ext(blogs, "Blogs") - - -Rel(anonymous_user, web_app, "Uses", "HTTPS") -Rel(aggregated_user, web_app, "Uses", "HTTPS") -Rel(administration_user, web_app, "Uses", "HTTPS") - -Rel(web_app, rel_db, "Reads from and writes to", "SQL/JDBC, post 3306") -Rel(web_app, filesystem, "Reads from") -Rel(web_app, nosql, "Reads from", "MongoDB wire protocol, port 27017") - -Rel_U(updater, rel_db, "Reads from and writes data to", "SQL/JDBC, post 3306") -Rel_U(updater, filesystem, "Writes to") -Rel_U(updater, nosql, "Reads from and writes to", "MongoDB wire protocol, port 27017") - -Rel(updater, twitter, "Gets profile information and tweets from", "HTTPS") -Rel(updater, github, "Gets information about public code repositories from", "HTTPS") -Rel(updater, blogs, "Gets content using RSS and Atom feeds from", "HTTP") - -Lay_R(rel_db, filesystem) - -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/diagram1.puml b/fineract-doc/src/docs/en/diagrams/samples/diagram1.puml deleted file mode 100644 index 3e008c63cc2..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/diagram1.puml +++ /dev/null @@ -1,104 +0,0 @@ -@startuml -' see: https://github.com/awslabs/aws-icons-for-plantuml -!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/master/dist -!includeurl AWSPuml/AWSCommon.puml -!includeurl AWSPuml/EndUserComputing/all.puml -!includeurl AWSPuml/Storage/SimpleStorageServiceS3.puml - -' see: https://github.com/aheil/EIP-PlantUML -!includeurl https://raw.githubusercontent.com/aheil/EIP-PlantUML/main/dist/EIP-PlantUML.puml - -' see: https://github.com/Roemer/plantuml-office -!define ICONURL https://raw.githubusercontent.com/Roemer/plantuml-office/master/office2014 -!includeurl ICONURL/Servers/database_server.puml -!includeurl ICONURL/Servers/application_server.puml -!includeurl ICONURL/Concepts/firewall_orange.puml -!includeurl ICONURL/Clouds/cloud_disaster_red.puml - -' https://github.com/tupadr3/plantuml-icon-font-sprites -!define ICONURL https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/v2.1.0 -!includeurl ICONURL/common.puml -!includeurl ICONURL/common.puml -!includeurl ICONURL/devicons/mysql.puml -!includeurl ICONURL/font-awesome/database.puml -!includeurl ICONURL/font-awesome-5/database.puml - -' https://github.com/rabelenda/cicon-plantuml-sprites -!define SPRITESURL https://raw.githubusercontent.com/rabelenda/cicon-plantuml-sprites/v1.0/sprites -!includeurl SPRITESURL/tomcat.puml -!includeurl SPRITESURL/kafka.puml -!includeurl SPRITESURL/java.puml -!includeurl SPRITESURL/cassandra.puml - -' https://github.com/RicardoNiepel/Azure-PlantUML -!define AzurePuml https://raw.githubusercontent.com/RicardoNiepel/Azure-PlantUML/release/2-1/dist -!includeurl AzurePuml/AzureCommon.puml -!includeurl AzurePuml/Databases/all.puml -!includeurl AzurePuml/Compute/AzureFunction.puml - -' Remove shadows -skinparam shadowing true - -actor "Person" as personAlias1 -AzureFunction(functionAlias, "Label", "Technology", "Optional Description") -AzureCosmosDb(cosmosDbAlias, "Label", "Technology", "Optional Description") - -personAlias1 --> functionAlias -functionAlias --> cosmosDbAlias - -rectangle "<$tomcat>\nwebapp" as webapp -queue "<$kafka>" as kafka -rectangle "<$java>\ndaemon" as daemon -database "<$cassandra>" as cassandra - -webapp -> kafka -kafka -> daemon -daemon --> cassandra - -DEV_MYSQL(db) -DEV_MYSQL(db4,label of db4,database,red) #DeepSkyBlue - -MsgChannel(channel1, "Channel 1") -MsgChannel(channel2, "Channel 2") -Message(msg, "Message") - -Send(channel1, msg) -Send(msg, channel2) - -rectangle System1 -rectangle System2 -rectangle System3 - -Message(source) -Message(target) - -ControlBus("cb") - -Send(source, System1) -Send(System1, System2) -Send(System2, System3) -Send(System3, target) - -System1 <--> cb -System2 <--> cb -System3 <--> cb - -SimpleStorageServiceS3(storageAlias, "Label", "Technology", "Optional Description") - -actor User -[Third party application] as ThirdPartyApp - -package "Spring Boot Application" { - rectangle Controllers <> - rectangle DataStoreService <> - rectangle Repository <> -} - -User --> Controllers -ThirdPartyApp --> Controllers -storageAlias --> Controllers - -Controllers --> DataStoreService -DataStoreService --> Repository - -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/icons.puml b/fineract-doc/src/docs/en/diagrams/samples/icons.puml deleted file mode 100644 index f9878e76483..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/icons.puml +++ /dev/null @@ -1,3 +0,0 @@ -@startuml -listopeniconic -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/logos.puml b/fineract-doc/src/docs/en/diagrams/samples/logos.puml deleted file mode 100644 index b0a880c1ff2..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/logos.puml +++ /dev/null @@ -1,27 +0,0 @@ -@startuml -!includeurl https://raw.githubusercontent.com/inthepocket/plantuml-styles/master/styles.plantuml!0 - -!include -!include -!include -!include - -title Gil Barbara's logos example - -' skinparam monochrome true - -rectangle "<$flask>\nwebapp" as webapp -queue "<$kafka>" as kafka -rectangle "<$kotlin>\ndaemon" as daemon -database "<$cassandra>" as cassandra - -webapp -> kafka -kafka -> daemon -daemon --> cassandra - -database "Database" -package "Package" -node "ParentNode" { -component "Subcomponent" -} -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/mindmap.puml b/fineract-doc/src/docs/en/diagrams/samples/mindmap.puml deleted file mode 100644 index 54627d79fef..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/mindmap.puml +++ /dev/null @@ -1,41 +0,0 @@ -@startmindmap -!includeurl https://cdn.jsdelivr.net/gh/HSLdevcom/hsl-plantuml-theme/hsl-theme.iuml - -title Crosscutting Concepts - -+ Crosscutting Concepts -++ Under the hood -+++ Persistence -+++ Process Control -+++ Transaction handling -+++ Session handling -+++ Communication and Integration -+++ Exception and error handling -+++ Parallelization / Threading -+++ Plausibility checks and validation -+++ Business Rules -+++ Batch -+++ Reporting -++ Operation Concepts -+++ Administration -+++ Management -+++ Disaster-Recovery -+++ Skalierung -+++ Clusterring -+++ Monitoring, Logging, Protocolling -+++ High Availability --- Domain Concepts --- Architecture and Design Patterns --- User Experience (UX) ---- User Interface ---- Ergonomics ---- Internationalization --- Security and Safety ---- Security ---- Safety --- Development Concepts ---- Build, Test, Deploy ---- Code generation ---- Migration ---- Configurability -@endmindmap \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/sequence1.puml b/fineract-doc/src/docs/en/diagrams/samples/sequence1.puml deleted file mode 100644 index 89b09ee225f..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/sequence1.puml +++ /dev/null @@ -1,35 +0,0 @@ -@startuml -'!includeurl https://cdn.jsdelivr.net/gh/HSLdevcom/hsl-plantuml-theme/hsl-theme.iuml -'!define LIGHTBLUE -'!includeurl https://raw.githubusercontent.com/Drakemor/RedDress-PlantUML/master/style.puml - -'!includeurl https://github.com/rbkmoney/plantuml-toolset/raw/master/skin.iwsd -'!includeurl https://raw.githubusercontent.com/inthepocket/plantuml-styles/master/styles.plantuml!0 - -!includeurl https://github.com/rbkmoney/plantuml-toolset/raw/master/super-serious-skin.iwsd - -skinparam BackgroundColor #ffffff - -title Publish(er) Jobs Workers (Successful Block Copy) - -hide footbox - -Alice -> Bob: Authentication Request -Bob --> Alice: Authentication Response - -Alice -> Bob: Another authentication Request -Alice <-- Bob: Another authentication Response - -Alice -> Bob: Authentication Request -... -Bob --> Alice: Authentication Response - activate Alice - note left of Alice - Drink that potion! - end note - deactivate Alice - activate Bob -... 5 minutes later ... -Bob --> Alice: Bye! - deactivate Bob -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/sprite.puml b/fineract-doc/src/docs/en/diagrams/samples/sprite.puml deleted file mode 100644 index 2f699883b16..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/sprite.puml +++ /dev/null @@ -1,3 +0,0 @@ -@startuml -listsprites -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/samples/test.puml b/fineract-doc/src/docs/en/diagrams/samples/test.puml deleted file mode 100644 index 15486f5f578..00000000000 --- a/fineract-doc/src/docs/en/diagrams/samples/test.puml +++ /dev/null @@ -1,3 +0,0 @@ -@startuml -testdot -@enduml \ No newline at end of file diff --git a/fineract-doc/src/docs/en/sample.adoc b/fineract-doc/src/docs/en/sample.adoc deleted file mode 100644 index 8ad0d5660dd..00000000000 --- a/fineract-doc/src/docs/en/sample.adoc +++ /dev/null @@ -1,25 +0,0 @@ -:doctitle: Fineract Documentation - -include::config.adoc[] - -= Fineract Documentation - -IMPORTANT: @vidakovic migrate old documentation! - -.Amazon -[plantuml,amazon,svg] ----- -include::diagrams/samples/amazon.puml[] ----- - -.C4 Diagram -[plantuml,c4,svg] ----- -include::diagrams/samples/c4.puml[] ----- - -.Mindmap -[plantuml,mindmap,svg] ----- -include::diagrams/samples/mindmap.puml[] ----- diff --git a/fineract-provider/.externalToolBuilders/OpenJPA Enhance Builder.launch b/fineract-provider/.externalToolBuilders/OpenJPA Enhance Builder.launch index a741f730b63..38eef72afe4 100644 --- a/fineract-provider/.externalToolBuilders/OpenJPA Enhance Builder.launch +++ b/fineract-provider/.externalToolBuilders/OpenJPA Enhance Builder.launch @@ -1,10 +1,10 @@ - - - - - - - + + + + + + + diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index 2e6fa024ee1..680875914dc 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -18,51 +18,98 @@ */ description = 'Fineract Provider' -apply plugin: 'rebel' -apply plugin: 'war' +apply plugin: 'org.zeroturnaround.gradle.jrebel' +apply plugin: 'java' +apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' -apply plugin: 'openjpa' apply plugin: 'com.gorylenko.gradle-git-properties' apply plugin: 'io.swagger.core.v3.swagger-gradle-plugin' -apply plugin: 'distribution' -apply plugin: 'signing' +apply plugin: 'com.google.cloud.tools.jib' -// Configuration for the OpenJPA enhance task -// https://github.com/radcortez/openjpa-gradle-plugin -System.setProperty("openjpa.Log", "commons") - -openjpa { - includes = [ +// Custom OpenJPA enhancement +compileJava.doLast { + def enhanceIncludes = [ '**/AbstractPersistableCustom.class', '**/domain/*.class' ] - enhance { - enforcePropertyRestrictions true + + def classesToEnhance = project.sourceSets.main.output.classesDirs.collectMany { classesDir -> + project.fileTree(classesDir).matching { + enhanceIncludes.forEach { include it } + }.files + } + + String persistenceXml = "META-INF/persistence.xml" + def persistenceXmlFile + + // Check if persistence.xml is in the resource dirs. + project.sourceSets.main.resources.srcDirs.collect { resourceDir -> + def persistenceXmlFileCandidate = project.fileTree(resourceDir).matching { + include persistenceXml + } + + if (!persistenceXmlFileCandidate.isEmpty()) { + if (persistenceXmlFile == null) { + persistenceXmlFile = persistenceXmlFileCandidate.singleFile + } else { + throw new InvalidUserDataException("Multiple persistence.xml files found in path: " + + persistenceXmlFile + ", " + persistenceXmlFileCandidate) + } + } + + // Nothing found. Fallback to plain file. + if (persistenceXmlFile == null) { + persistenceXmlFile = project.file(persistenceXml) + if (!persistenceXmlFile.exists()) { + throw new InvalidUserDataException( + "Could not find valid persistence.xml in path " + this.persistenceXml) + } + } + } + + org.apache.openjpa.lib.util.Options options = new org.apache.openjpa.lib.util.Options([ + "addDefaultConstructor" : true, + "enforcePropertyRestrictions": true, + "tmpClassLoader" : false, + "propertiesFile" : persistenceXmlFile + ]) + + def classes = project.sourceSets.main.output.classesDirs.collect { it.toURI().toURL() } + + def compileJars = project.configurations.compileClasspath.files.collect { jar -> + jar.toURI().toURL() } + + def resources = project.sourceSets.main.resources.srcDirs.collect { resource -> + resource.toURI().toURL() + } + + def urls = (classes + compileJars + resources) + + ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(new URLClassLoader((URL[])urls, currentClassLoader)); + org.apache.openjpa.enhance.PCEnhancer.run((String []) classesToEnhance, options); + Thread.currentThread().setContextClassLoader(currentClassLoader); } // Configuration for Swagger documentation generation task // https://github.com/swagger-api/swagger-core/tree/master/modules/swagger-gradle-plugin import org.apache.tools.ant.filters.ReplaceTokens -task prepareInputYaml(dependsOn: 'generateGitProperties') { - doLast { - def versionForSwagger - - if(project.ext.properties.containsKey("gitProps")) - versionForSwagger = project.ext.gitProps['git.commit.id.describe'] - else - versionForSwagger = "unknown" +task prepareInputYaml { + outputs.file('config/swagger/fineract-input.yaml') + doLast { copy { from file('config/swagger/fineract-input.yaml.template') into file('config/swagger') rename { String filename -> return 'fineract-input.yaml' } - filter(ReplaceTokens, tokens: [VERSION: versionForSwagger]) + filter(ReplaceTokens, tokens: [VERSION: "${project.version}".toString()]) } } } +rat.dependsOn prepareInputYaml resolve { logging.captureStandardOutput LogLevel.INFO @@ -70,7 +117,7 @@ resolve { outputFormat = 'YAML' prettyPrint = 'TRUE' classpath = sourceSets.main.runtimeClasspath - outputDir = file("${buildDir}/classes/java/main/static/swagger-ui") + outputDir = file("${buildDir}/generated/swagger-ui") openApiFile = file("config/swagger/fineract-input.yaml") } @@ -98,45 +145,6 @@ configurations { } apply from: 'dependencies.gradle' -/* TODO https://issues.apache.org/jira/browse/FINERACT-939 (dev-dependencies.gradle has been removed, as totally broken) - // Pick up dependencies based on the environment, defaults to production - if (project.hasProperty('env') && project.getProperty('env') == 'dev') { - apply from: 'dev-dependencies.gradle' - } else { - apply from: 'dependencies.gradle' - } - */ - -/* Enable Oauth2 authentication based on environment, default to HTTP basic auth */ -if (project.hasProperty('security') && project.getProperty('security') == 'oauth') { - if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') { - copy { - from './properties/oauth/twofactor/' - into 'src/main/resources/' - include '*.properties' - } - } else { - copy { - from './properties/oauth/' - into 'src/main/resources/' - include '*.properties' - } - } -} else { - if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') { - copy { - from './properties/basicauth/twofactor/' - into 'src/main/resources/' - include '*.properties' - } - } else { - copy { - from './properties/basicauth/' - into 'src/main/resources/' - include '*.properties' - } - } -} // Configuration for the modernizer plugin // https://github.com/andygoossens/gradle-modernizer-plugin @@ -152,42 +160,26 @@ compileJava { finalizedBy resolve } -war { - from("$rootDir/licenses/binary/") { - // notice the parens - into "WEB-INF/licenses/binary/" // no leading slash - } - from("$rootDir/LICENSE_RELEASE") { - // notice the parens - into "WEB-INF/" // no leading slash - } - from("$rootDir/NOTICE_RELEASE") { - // notice the parens - into "WEB-INF/" // no leading slash - } - rename ('LICENSE_RELEASE', 'LICENSE') - rename ('NOTICE_RELEASE', 'NOTICE') +// If we are running Gradle within Eclipse to enhance classes with OpenJPA, +// set the classes directory to point to Eclipse's default build directory +if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') +{ + sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-provider/bin/main") +} - from("$rootDir/DISCLAIMER") { - // notice the parens - into "WEB-INF/" // no leading slash +eclipse +{ + project { + buildCommand([ LaunchConfigHandle: "/.externalToolBuilders/OpenJPA Enhance Builder.launch" ], 'org.eclipse.ui.externaltools.ExternalToolBuilder') } - enabled = true } /* http://stackoverflow.com/questions/19653311/jpa-repository-works-in-idea-and-production-but-not-in-gradle */ sourceSets.main.output.resourcesDir = sourceSets.main.java.outputDir sourceSets.test.output.resourcesDir = sourceSets.test.java.outputDir -/* Exclude maria db related files for non dev builds */ if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) { sourceSets { - main { - java { - exclude '**/ServerWithMariaDB*' - exclude '**/MariaDB4j*' - } - } test { java { exclude '**/core/boot/tests/**' @@ -207,7 +199,7 @@ configurations { driver } dependencies { - driver 'org.drizzle.jdbc:drizzle-jdbc:1.4' + driver 'org.mariadb.jdbc:mariadb-java-client:2.7.4' } URLClassLoader loader = GroovyObject.class.classLoader @@ -218,7 +210,7 @@ configurations.driver.each {File file -> task createDB { description= "Creates the Database. Needs database name to be passed (like: -PdbName=someDBname)" doLast { - def sql = Sql.newInstance( 'jdbc:mysql:thin://localhost:3306/', mysqlUser, mysqlPassword, 'org.drizzle.jdbc.DrizzleDriver' ) + def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3306/', mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' ) sql.execute( 'create database '+"`$dbName`" ) } } @@ -226,13 +218,13 @@ task createDB { task dropDB { description= "Drops the specified database. The database name has to be passed (like: -PdbName=someDBname)" doLast { - def sql = Sql.newInstance( 'jdbc:mysql:thin://localhost:3306/', mysqlUser, mysqlPassword, 'org.drizzle.jdbc.DrizzleDriver' ) + def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3306/', mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' ) sql.execute( 'DROP DATABASE '+"`$dbName`") } } task setBlankPassword { doLast { - def sql = Sql.newInstance( 'jdbc:mysql:thin://localhost:3306/', mysqlUser, mysqlPassword, 'org.drizzle.jdbc.DrizzleDriver' ) + def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3306/', mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' ) sql.execute('USE `fineract_tenants`') sql.execute('UPDATE fineract_tenants.tenants SET schema_server = \'localhost\', schema_server_port = \'3306\', schema_username = \'mifos\', schema_password = \'mysql\' WHERE id=1;') } @@ -242,103 +234,117 @@ bootRun { jvmArgs = [ "-Dspring.output.ansi.enabled=ALWAYS" ] + + dependencies { + implementation 'org.mariadb.jdbc:mariadb-java-client:2.7.4' + } + + classpath += files("build/generated/swagger-ui") } springBoot { - mainClassName = 'org.apache.fineract.ServerApplication' + mainClass = 'org.apache.fineract.ServerApplication' } bootJar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { - attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher' + attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher', 'Implementation-Title': 'Apache Fineract', 'Implementation-Version': archiveVersion } -} + dependsOn resolve + classifier = '' -bootWar { - enabled = false + from('build/generated/swagger-ui') { + into 'BOOT-INF/classes/static/swagger-ui' + } } -tasks.withType(Tar) { - compression Compression.GZIP - extension 'tar.gz' +jar { + from('build/generated/swagger-ui') { + include '*' + } } -distributions { - binary { - baseName "apache-fineract-$releaseVersion-binary" - contents { - from bootJar - from war - // TODO: @vidakovic add client library - from("$rootDir/licenses/binary/") { - into "licenses/binary/" - } - from "$rootDir/LICENSE_RELEASE" - from "$rootDir/NOTICE_RELEASE" - rename ('LICENSE_RELEASE', 'LICENSE') - rename ('NOTICE_RELEASE', 'NOTICE') +jar.dependsOn resolve +jar.dependsOn bootJar - from "$rootDir/DISCLAIMER" - from "$rootDir/README.md" - } - } - src { - baseName "apache-fineract-$releaseVersion-src" - contents { - from "$rootDir/" - exclude '**/build' , '.git', '.gradle', '.github', '.settings', '.project', '.classpath', '.idea', 'out', '._.DS_Store', '.DS_Store', 'WebContent', '.externalToolbuilders', '.theia', '.gitpod.yml', '.travis.yml', 'LICENSE_RELEASE', 'NOTICE_RELEASE', '**/licenses/binary' - rename ('LICENSE_SOURCE', 'LICENSE') - rename ('NOTICE_SOURCE', 'NOTICE') - } +jib { + from { + image = 'azul/zulu-openjdk-alpine:17' } -} -tasks.binaryDistZip.enabled false -tasks.srcDistZip.enabled false + to { + image = 'fineract' + tags = [ + "${version}", + 'latest' + ] + } -// create signatures and checksums only if project parameter "fineract.release" is provided on the command line -if( project.hasProperty("fineract.release") ) { - signing { - useGpgCmd() - sign (binaryDistTar, srcDistTar) + container { + creationTime = 'USE_CURRENT_TIMESTAMP' + mainClass = 'org.apache.fineract.ServerApplication' + jvmFlags = [ + '-Xmx1G', + '-Xms1G', + '-XshowSettings:vm', + '-XX:+UseContainerSupport', + '-XX:+UseStringDeduplication', + '-XX:MinRAMPercentage=25', + '-XX:MaxRAMPercentage=80', + '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED', + '--add-opens=java.base/java.lang=ALL-UNNAMED', + '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED', + '--add-opens=java.base/java.io=ALL-UNNAMED', + '--add-opens=java.base/java.security=ALL-UNNAMED', + '--add-opens=java.base/java.util=ALL-UNNAMED', + '--add-opens=java.management/javax.management=ALL-UNNAMED', + '--add-opens=java.naming/javax.naming=ALL-UNNAMED' + ] + args = [ + '-Duser.home=/tmp', + '-Dfile.encoding=UTF-8', + '-Duser.timezone=UTC', + '-Djava.security.egd=file:/dev/./urandom' + ] + ports = ['8080/tcp', '8443/tcp'] + labels = [maintainer: 'Aleksandar Vidakovic '] + user = 'nobody:nogroup' } - tasks.withType(Tar) { task -> - task.doLast { - ant.checksum file: task.archivePath, algorithm: 'SHA-512', fileext: '.sha512' - } + + allowInsecureRegistries = true + + dependencies { + implementation 'org.mariadb.jdbc:mariadb-java-client:2.7.4' } - tasks.withType(Sign) { task -> - task.doLast { - task.getFilesToSign().each { f -> - new ByteArrayOutputStream().withStream { os -> - def result = exec { - workingDir "$buildDir/distributions" - executable 'sh' - args '-c', "gpg --verify ${f}.asc" - standardOutput = os - } - if(result.exitValue==0) { - println '+++ GPG signature correct!' - } else { - println '--- GPG signature incorrect!' - throw new RuntimeException('--- GPG signature incorrect!') - } - } + + extraDirectories { + paths { + path { + from = file('build/generated/swagger-ui') + into = "/app/classes" } } } } +tasks.jibDockerBuild.dependsOn = ["bootJar"] + // Configuration for git properties gradle plugin // https://github.com/n0mer/gradle-git-properties gitProperties { - gitPropertiesResourceDir = "$buildDir/classes/java/main/resources" + gitPropertiesResourceDir = file("$buildDir/classes/java/main") dateFormat = "yyyy-MM-dd'T'HH:mmZ" dateFormatTimeZone = "GMT" failOnNoGitDirectory = false - extProperty = 'gitProps' } // make sure the generateGitProperties task always executes (even when git.properties is not changed) generateGitProperties.outputs.upToDateWhen { false } + +// Gradle 7.x asks for explicit dependencies between tasks +checkstyleMain.dependsOn resolve +checkstyleTest.dependsOn resolve +licenseMain.dependsOn processResources, generateGitProperties +spotbugsTest.dependsOn resolve +test.dependsOn resolve diff --git a/fineract-provider/config/swagger/fineract-input.yaml.template b/fineract-provider/config/swagger/fineract-input.yaml.template index d6b04608851..63268f008c0 100644 --- a/fineract-provider/config/swagger/fineract-input.yaml.template +++ b/fineract-provider/config/swagger/fineract-input.yaml.template @@ -22,7 +22,7 @@ info: name: Apache 2.0 url: 'http://www.apache.org/licenses/LICENSE-2.0.html' servers: - - url: https://localhost:8443/fineract-provider/api/v1 + - url: /fineract-provider/api/v1 - url: https://demo.fineract.dev/fineract-provider/api/v1 components: securitySchemes: diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 6d613183a4f..393562ffbdf 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + dependencies { providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") @@ -26,34 +27,26 @@ dependencies { // implementation dependencies are directly used (compiled against) in src/main (and src/test) // implementation( - //'ch.vorburger.mariaDB4j:mariaDB4j:2.4.0', - '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-oauth2-resource-server', + + 'org.glassfish.jersey.media:jersey-media-multipart:2.35', 'org.springframework:spring-jms', 'org.springframework:spring-context-support', - 'org.springframework.security.oauth:spring-security-oauth2', - - 'com.sun.jersey:jersey-servlet', - 'com.sun.jersey:jersey-server', - 'com.sun.jersey:jersey-json', - 'com.sun.jersey.contribs:jersey-spring', - 'com.sun.jersey.contribs:jersey-multipart', 'com.google.guava:guava', 'com.google.code.gson:gson', - 'com.sun.jersey:jersey-core', + 'jakarta.jms:jakarta.jms-api', 'jakarta.management.j2ee:jakarta.management.j2ee-api', - 'jakarta.validation:jakarta.validation-api', 'com.squareup.retrofit2:retrofit', 'com.squareup.okhttp3:okhttp', 'com.squareup.okhttp3:okhttp-urlconnection', - 'org.apache.commons:commons-email', 'org.apache.commons:commons-lang3', 'commons-io:commons-io', 'org.apache.poi:poi', @@ -61,25 +54,32 @@ dependencies { 'org.apache.poi:poi-ooxml-schemas', 'org.apache.tika:tika-core', - 'org.drizzle.jdbc:drizzle-jdbc', 'org.flywaydb:flyway-core', 'com.github.librepdf:openpdf', 'com.github.spullara.mustache.java:compiler', 'com.jayway.jsonpath:json-path', - 'org.dom4j:dom4j', - 'javax.cache:cache-api', 'com.github.spotbugs:spotbugs-annotations', 'io.swagger.core.v3:swagger-annotations', 'org.webjars:webjars-locator-core', - 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.3.0', + 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.4.0', 'com.squareup.retrofit2:converter-gson', - 'com.sun.activation:jakarta.activation:1.2.2' + 'org.apache.commons:commons-email' ) + + implementation('org.springframework.boot:spring-boot-starter-jersey') { + exclude group: 'org.glassfish.hk2.external', module: 'aopalliance-repackaged' + exclude group: 'org.glassfish.hk2', module: 'hk2-runlevel' + exclude group: 'org.hibernate.validator', module: 'hibernate-validator' + exclude group: 'jakarta.activation', module: 'jakarta.activation-api' + } + implementation('org.dom4j:dom4j') { + exclude group: 'javax.xml.bind' + } implementation ('jakarta.xml.bind:jakarta.xml.bind-api') { exclude group: 'jakarta.activation' } @@ -101,9 +101,14 @@ dependencies { } implementation ('org.mnode.ical4j:ical4j') { exclude group: 'commons-logging' + exclude group: 'javax.activation' + } + + implementation ('org.ehcache:ehcache') { + exclude group: 'com.sun.activation' } - runtimeOnly('org.ehcache:ehcache') { + runtimeOnly('org.glassfish.jaxb:jaxb-runtime') { exclude group: 'com.sun.activation' } @@ -112,7 +117,6 @@ dependencies { 'org.apache.bval:org.apache.bval.bundle', 'org.springframework.boot:spring-boot-starter-actuator', 'org.webjars.npm:swagger-ui-dist', - 'org.glassfish.jaxb:jaxb-runtime', // Although fineract (at the time of writing) doesn't have any compile time dep. on httpclient, // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility diff --git a/fineract-provider/properties/basicauth/application.properties b/fineract-provider/properties/basicauth/application.properties deleted file mode 100644 index 4a0e1dd9c27..00000000000 --- a/fineract-provider/properties/basicauth/application.properties +++ /dev/null @@ -1,32 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -spring.profiles.default=basicauth -management.health.jms.enabled=false - -# FINERACT 1296 -management.endpoint.health.probes.enabled=true -management.health.livenessState.enabled=true -management.health.readinessState.enabled=true - -# FINERACT-883 -management.info.git.mode=FULL - -# FINERACT-914 -server.forward-headers-strategy=framework diff --git a/fineract-provider/properties/oauth/application.properties b/fineract-provider/properties/oauth/application.properties deleted file mode 100644 index fbe9c9f53f1..00000000000 --- a/fineract-provider/properties/oauth/application.properties +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -spring.profiles.default=basicauth -spring.profiles.active=oauth -management.health.jms.enabled=false - -# FINERACT-883 -management.info.git.mode=FULL - -# FINERACT-914 -server.forward-headers-strategy=framework -spring.cache.jcache.config=classpath:/META-INF/spring/ehcache.xml diff --git a/fineract-provider/properties/oauth/twofactor/application.properties b/fineract-provider/properties/oauth/twofactor/application.properties deleted file mode 100644 index 4e4ecd39f1e..00000000000 --- a/fineract-provider/properties/oauth/twofactor/application.properties +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -spring.profiles.default=basicauth -spring.profiles.active=oauth,twofactor -management.health.jms.enabled=false - -# FINERACT-883 -management.info.git.mode=FULL - -# FINERACT-914 -server.forward-headers-strategy=framework -spring.cache.jcache.config=classpath:/META-INF/spring/ehcache.xml diff --git a/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java b/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java index aad208e5b97..f567a6c2a42 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java +++ b/fineract-provider/src/main/java/org/apache/fineract/ServerApplication.java @@ -20,13 +20,10 @@ import java.io.IOException; import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration; -import org.apache.fineract.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportResource; /** * Fineract main() application which launches Fineract in an embedded Tomcat HTTP (using Spring Boot). @@ -39,13 +36,10 @@ * * It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Fineract! ;-) * - * @see ServerWithMariaDB4jApplication for an alternative with an embedded DB */ public class ServerApplication extends SpringBootServletInitializer { - @Import({ EmbeddedTomcatWithSSLConfiguration.class }) - @ImportResource({ "classpath*:META-INF/spring/hikariDataSource.xml" }) private static class Configuration extends AbstractApplicationConfiguration {} @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/ServerWithMariaDB4jApplication.java b/fineract-provider/src/main/java/org/apache/fineract/ServerWithMariaDB4jApplication.java deleted file mode 100644 index 3a6521de011..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/ServerWithMariaDB4jApplication.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract; - -import java.awt.Desktop; -import java.io.IOException; -import java.net.URI; - -import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration; -import org.apache.fineract.infrastructure.core.boot.ApplicationExitUtil; -import org.apache.fineract.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration; -import org.apache.fineract.infrastructure.core.boot.db.MariaDB4jDataSourceConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Import; -import org.springframework.core.io.Resource; - -/** - * Fineract main() application which launches Fineract in an embedded Tomcat HTTP - * server (using Spring Boot), as well as an embedded database (using - * MariaDB4j). - * - * You can easily launch this via Debug as Java Application in your IDE - - * without needing command line Gradle stuff, no need to build and deploy a WAR, - * remote attachment etc. - * - * It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Fineract! ;-) - * - * @see ServerApplication for the same without the embedded MariaDB4j database - */ -public class ServerWithMariaDB4jApplication { - private final static Logger LOG = LoggerFactory.getLogger(ServerWithMariaDB4jApplication.class); - - @Import({ MariaDB4jDataSourceConfiguration.class, EmbeddedTomcatWithSSLConfiguration.class }) - public static class Configuration extends AbstractApplicationConfiguration { } - - public static void main(String[] args) throws Exception { - ConfigurableApplicationContext ctx = SpringApplication.run(Configuration.class, args); - if (!Desktop.isDesktopSupported()) { - LOG.info("Not going to open UI homepage in local web browser, because !Desktop.isDesktopSupported()"); - - } else { - // apps/community-app/dist/community-app/index.html - Resource distResource = ctx.getResource("file:" + System.getProperty("user.dir") + - System.getProperty("file.separator") + "apps" + - System.getProperty("file.separator") + "community-app" + - System.getProperty("file.separator") + "dist" + - System.getProperty("file.separator") + "community-app" + - System.getProperty("file.separator") + "index.html"); - URI distURI = URI.create("https://localhost:8443/fineract-provider" + - "/apps/community-app/index.html?baseApiUrl=https://localhost:8443" + - "&tenantIdentifier=default#/"); - - // apps/community-app/app/index.html - Resource devResource = ctx.getResource("file:" + System.getProperty("user.dir") + - System.getProperty("file.separator") + "apps" + - System.getProperty("file.separator") + "community-app" + - System.getProperty("file.separator") + "app" + - System.getProperty("file.separator") + "index.html"); - URI devURI = URI.create("https://localhost:8443/fineract-provider" + - "/apps/community-app/app/index.html?baseApiUrl=https://localhost:8443" + - "&tenantIdentifier=default#/"); - - if (distResource.exists()) { - openWebBrowser(distURI); - } else if (devResource.exists()) { - openWebBrowser(devURI); - } else { - LOG.error("Cannot open Fineract UI in browser; not found: " + distResource.toString()); - } - } - - // TODO Tray Icon stuff; dig out my very own old @see https://github.com/mifos/head/tree/hudsonBuild-MIFOS-5157_Launch4j-EXE_NewDist-squash1/server-jetty/src/main/java/org/mifos/server/tray - - ApplicationExitUtil.waitForKeyPressToCleanlyExit(ctx); - } - - private static void openWebBrowser(URI uri) { - try { - LOG.info("Opening Fineract UI in browser: " + uri.toString()); - Desktop.getDesktop().browse(uri); - } catch (IOException e) { - LOG.error("IOException when opening Fineract UI in browser: " + uri.toString(), e); - } - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java index b1da6b7d434..d6a23a3e106 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformService.java @@ -43,4 +43,8 @@ public interface AccountingDropdownReadPlatformService { Map> retrieveAccountMappingOptionsForShareProducts(); + List retrieveExpenseAccountOptions(); + + List retrieveAssetAccountOptions(); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java index d73a82408e3..6e0014c0e40 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingDropdownReadPlatformServiceImpl.java @@ -95,6 +95,16 @@ public Map> retrieveAccountMappingOptions() { includeEquityAccounts); } + @Override + public List retrieveExpenseAccountOptions() { + return accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.EXPENSE); + } + + @Override + public List retrieveAssetAccountOptions() { + return accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.ASSET); + } + private Map> retrieveAccountMappingOptions(boolean includeAssetAccounts, boolean includeIncomeAccounts, boolean includeExpenseAccounts, boolean includeLiabilityAccounts, boolean includeEquityAccounts) { final Map> accountOptions = new HashMap<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java index 17e93541666..aae8b0cfd7c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java @@ -18,8 +18,6 @@ */ package org.apache.fineract.accounting.glaccount.api; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataParam; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -68,6 +66,8 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java index 0859cb2ebd2..ef3d82a152f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/data/GLAccountData.java @@ -69,6 +69,39 @@ public static GLAccountData importInstance(String name, Long parentId, String gl return new GLAccountData(name, parentId, glCode, manualEntriesAllowed, type, usage, description, tagId, rowIndex); } + public static GLAccountData createFrom(final Long id) { + return new GLAccountData(id); + } + + private GLAccountData(final Long id) { + + this.name = null; + this.parentId = null; + this.glCode = null; + this.manualEntriesAllowed = null; + this.type = null; + this.usage = null; + this.description = null; + this.tagId = null; + this.rowIndex = null; + this.id = id; + this.disabled = null; + this.nameDecorated = null; + this.organizationRunningBalance = null; + this.accountTypeOptions = null; + this.usageOptions = null; + this.assetHeaderAccountOptions = null; + this.liabilityHeaderAccountOptions = null; + this.equityHeaderAccountOptions = null; + this.incomeHeaderAccountOptions = null; + this.expenseHeaderAccountOptions = null; + this.allowedAssetsTagOptions = null; + this.allowedLiabilitiesTagOptions = null; + this.allowedEquityTagOptions = null; + this.allowedIncomeTagOptions = null; + this.allowedExpensesTagOptions = null; + } + private GLAccountData(String name, Long parentId, String glCode, Boolean manualEntriesAllowed, EnumOptionData type, EnumOptionData usage, String description, CodeValueData tagId, Integer rowIndex) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.java index a309f066b6e..59dd985a7cf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepository.java @@ -18,9 +18,13 @@ */ package org.apache.fineract.accounting.glaccount.domain; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface GLAccountRepository extends JpaRepository, JpaSpecificationExecutor { + // no added behaviour + // adding behaviour to fetch id by glcode for opening balance bulk import + Optional findOneByGlCode(String glCode); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java index d1973a8296d..b66345ef672 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/domain/GLAccountRepositoryWrapper.java @@ -41,4 +41,9 @@ public GLAccount findOneWithNotFoundDetection(final Long id) { return this.repository.findById(id).orElseThrow(() -> new GLAccountNotFoundException(id)); } + // finding account id by glcode for opening balance bulk import + public GLAccount findOneByGlCodeWithNotFoundDetection(final String glCode) { + return this.repository.findOneByGlCode(glCode).orElseThrow(() -> new GLAccountNotFoundException(glCode)); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java index 01b7c632237..1e4c450a8fe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/glaccount/exception/GLAccountNotFoundException.java @@ -33,4 +33,8 @@ public GLAccountNotFoundException(final Long id) { public GLAccountNotFoundException(final Long id, EmptyResultDataAccessException e) { super("error.msg.glaccount.id.invalid", "General Ledger account with identifier " + id + " does not exist ", id, e); } + + public GLAccountNotFoundException(final String glCode) { + super("error.msg.glaccount.code.invalid", "General Ledger account with GlCode " + glCode + " does not exist ", glCode); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java index 8c0f1fb9194..e9c8373cd32 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java @@ -19,11 +19,11 @@ package org.apache.fineract.accounting.journalentry.api; import java.time.LocalDate; +import java.time.ZoneId; import java.util.Date; import java.util.Locale; import javax.ws.rs.WebApplicationException; import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper; -import org.apache.fineract.infrastructure.core.service.DateUtils; /** * Class for parsing dates sent as query parameters @@ -41,6 +41,6 @@ public DateParam(final String dateStr) throws WebApplicationException { public Date getDate(final String parameterName, final String dateFormat, final String localeAsString) { final Locale locale = JsonParserHelper.localeFromString(localeAsString); final LocalDate localDate = JsonParserHelper.convertFrom(this.dateAsString, parameterName, dateFormat, locale); - return Date.from(localDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java index 92cf160dc25..064d41a8139 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java @@ -18,8 +18,6 @@ */ package org.apache.fineract.accounting.journalentry.api; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataParam; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -66,6 +64,8 @@ import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.java index 27efe3bd180..91542869de7 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/ChargePaymentDTO.java @@ -30,6 +30,7 @@ public ChargePaymentDTO(final Long chargeId, final Long loanChargeId, final BigD this.chargeId = chargeId; this.amount = amount; this.loanChargeId = loanChargeId; + } public Long getChargeId() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java index 3c00ab31c42..3fe0179435e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/JournalEntryData.java @@ -90,6 +90,49 @@ public class JournalEntryData { private String routingCode; private String receiptNumber; private String bankNumber; + private transient Long savingTransactionId; + + // for opening bal bulk import + public JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, List credits, + List debits, String locale, String dateFormat) { + this.officeId = officeId; + this.dateFormat = dateFormat; + this.locale = locale; + this.transactionDate = transactionDate; + this.currencyCode = currencyCode; + this.credits = credits; + this.debits = debits; + this.rowIndex = null; + this.paymentTypeId = null; + this.accountNumber = null; + this.checkNumber = null; + this.routingCode = null; + this.receiptNumber = null; + this.bankNumber = null; + this.comments = null; + this.id = null; + this.officeName = null; + this.glAccountName = null; + this.glAccountId = null; + this.glAccountCode = null; + this.glAccountType = null; + this.entryType = null; + this.amount = null; + this.currency = null; + this.transactionId = null; + this.manualEntry = null; + this.entityType = null; + this.entityId = null; + this.createdByUserId = null; + this.createdDate = null; + this.createdByUserName = null; + this.reversed = null; + this.referenceNumber = null; + this.officeRunningBalance = null; + this.organizationRunningBalance = null; + this.runningBalanceComputed = null; + this.transactionDetails = null; + } public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId, Integer rowIndex, List credits, List debits, String accountNumber, String checkNumber, @@ -98,6 +141,11 @@ public static JournalEntryData importInstance(Long officeId, LocalDate transacti checkNumber, routingCode, receiptNumber, bankNumber, comments, locale, dateFormat); } + public static JournalEntryData importInstance1(Long officeId, LocalDate transactionDate, String currencyCode, List credits, + List debits, String locale, String dateFormat) { + return new JournalEntryData(officeId, transactionDate, currencyCode, credits, debits, locale, dateFormat); + } + private JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId, Integer rowIndex, List credits, List debits, String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, String comments, String locale, String dateFormat) { @@ -192,6 +240,40 @@ public JournalEntryData(final Long id, final Long officeId, final String officeN this.currency = currency; } + public JournalEntryData(final Long id, final Long officeId, final String glAccountName, final Long glAccountId, + final String glAccountCode, final EnumOptionData glAccountClassification, final LocalDate transactionDate, + final EnumOptionData entryType, final BigDecimal amount, final String transactionId, final Boolean manualEntry, + final EnumOptionData entityType, final Long entityId, final LocalDate createdDate, final String currencyCode, + final Long savingTransactionId) { + this.id = id; + this.officeId = officeId; + this.officeName = null; + this.glAccountName = glAccountName; + this.glAccountId = glAccountId; + this.glAccountCode = glAccountCode; + this.glAccountType = glAccountClassification; + this.transactionDate = transactionDate; + this.entryType = entryType; + this.amount = amount; + this.transactionId = transactionId; + this.savingTransactionId = savingTransactionId; + this.manualEntry = manualEntry; + this.entityType = entityType; + this.entityId = entityId; + this.createdByUserId = null; + this.createdDate = createdDate; + this.createdByUserName = null; + this.comments = null; + this.reversed = false; + this.referenceNumber = null; + this.officeRunningBalance = null; + this.organizationRunningBalance = null; + this.runningBalanceComputed = null; + this.transactionDetails = null; + this.currency = null; + this.currencyCode = currencyCode; + } + public static JournalEntryData fromGLAccountData(final GLAccountData glAccountData) { final Long id = null; @@ -252,4 +334,33 @@ public Long getOfficeId() { public String getTransactionId() { return transactionId; } + + public Long getSavingTransactionId() { + return this.savingTransactionId; + } + + public String getCurrencyCode() { + return this.currencyCode; + } + + public boolean isManualEntry() { + return this.manualEntry; + } + + public EnumOptionData getEntityType() { + return this.entityType; + } + + public Long getEntityId() { + return this.entityId; + } + + public LocalDate getCreatedDate() { + return this.createdDate; + } + + public Long getPaymentTypeId() { + return this.paymentTypeId; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java index 3aab4a19a27..78f82ce14dd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java @@ -222,4 +222,12 @@ public Long getShareTransactionId() { return this.shareTransactionId; } + public boolean isReversed() { + return this.reversed; + } + + public String getDescription() { + return this.description; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java index dbaf6e1706b..8816d58f050 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavings.java @@ -23,5 +23,4 @@ public interface AccountingProcessorForSavings { void createJournalEntriesForSavings(SavingsDTO savingsDTO); - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index e51ba81fbe2..03e524725eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -20,11 +20,13 @@ import java.math.BigDecimal; import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.sql.DataSource; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.closure.domain.GLClosure; import org.apache.fineract.accounting.closure.domain.GLClosureRepository; @@ -58,12 +60,13 @@ import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; -import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; +import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.client.domain.ClientTransaction; import org.apache.fineract.portfolio.client.domain.ClientTransactionRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData; @@ -74,7 +77,11 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service @@ -95,6 +102,10 @@ public class AccountingProcessorHelper { private final ClientTransactionRepositoryWrapper clientTransactionRepository; private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; + private final ChargeRepositoryWrapper chargeRepositoryWrapper; + private final JdbcTemplate jdbcTemplate; + private final DataSource dataSource; + private static final Logger LOG = LoggerFactory.getLogger(AccountingProcessorHelper.class); @Autowired public AccountingProcessorHelper(final JournalEntryRepository glJournalEntryRepository, @@ -104,8 +115,10 @@ public AccountingProcessorHelper(final JournalEntryRepository glJournalEntryRepo final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository, final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final GLAccountRepositoryWrapper accountRepositoryWrapper, - final ClientTransactionRepositoryWrapper clientTransactionRepositoryWrapper) { + final ClientTransactionRepositoryWrapper clientTransactionRepositoryWrapper, + final ChargeRepositoryWrapper chargeRepositoryWrapper, final RoutingDataSource dataSource) { this.glJournalEntryRepository = glJournalEntryRepository; + this.dataSource = dataSource; this.accountMappingRepository = accountMappingRepository; this.closureRepository = closureRepository; this.officeRepositoryWrapper = officeRepositoryWrapper; @@ -115,6 +128,8 @@ public AccountingProcessorHelper(final JournalEntryRepository glJournalEntryRepo this.accountTransfersReadPlatformService = accountTransfersReadPlatformService; this.accountRepositoryWrapper = accountRepositoryWrapper; this.clientTransactionRepository = clientTransactionRepositoryWrapper; + this.chargeRepositoryWrapper = chargeRepositoryWrapper; + this.jdbcTemplate = new JdbcTemplate(this.dataSource); } public LoanDTO populateLoanDtoFromMap(final Map accountingBridgeData, final boolean cashBasedAccountingEnabled, @@ -132,8 +147,7 @@ public LoanDTO populateLoanDtoFromMap(final Map accountingBridge for (final Map map : newTransactionsMap) { final Long transactionOfficeId = (Long) map.get("officeId"); final String transactionId = ((Long) map.get("id")).toString(); - final Date transactionDate = Date - .from(((LocalDate) map.get("date")).atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + final Date transactionDate = Date.from(((LocalDate) map.get("date")).atStartOfDay(ZoneId.systemDefault()).toInstant()); final LoanTransactionEnumData transactionType = (LoanTransactionEnumData) map.get("type"); final BigDecimal amount = (BigDecimal) map.get("amount"); final BigDecimal principal = (BigDecimal) map.get("principalPortion"); @@ -200,8 +214,7 @@ public SavingsDTO populateSavingsDtoFromMap(final Map accounting for (final Map map : newTransactionsMap) { final Long transactionOfficeId = (Long) map.get("officeId"); final String transactionId = ((Long) map.get("id")).toString(); - final Date transactionDate = Date - .from(((LocalDate) map.get("date")).atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + final Date transactionDate = Date.from(((LocalDate) map.get("date")).atStartOfDay(ZoneId.systemDefault()).toInstant()); final SavingsAccountTransactionEnumData transactionType = (SavingsAccountTransactionEnumData) map.get("type"); final BigDecimal amount = (BigDecimal) map.get("amount"); final boolean reversed = (Boolean) map.get("reversed"); @@ -270,8 +283,7 @@ public SharesDTO populateSharesDtoFromMap(final Map accountingBr for (final Map map : newTransactionsMap) { final Long transactionOfficeId = (Long) map.get("officeId"); final String transactionId = ((Long) map.get("id")).toString(); - final Date transactionDate = Date - .from(((LocalDate) map.get("date")).atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + final Date transactionDate = Date.from(((LocalDate) map.get("date")).atStartOfDay(ZoneId.systemDefault()).toInstant()); final ShareAccountTransactionEnumData transactionType = (ShareAccountTransactionEnumData) map.get("type"); final ShareAccountTransactionEnumData transactionStatus = (ShareAccountTransactionEnumData) map.get("status"); final BigDecimal amount = (BigDecimal) map.get("amount"); @@ -308,7 +320,7 @@ public ClientTransactionDTO populateClientTransactionDtoFromMap(final Map chargePaymentDTOs) { @@ -777,9 +789,9 @@ public void createCashBasedJournalEntriesAndReversalsForSavingsCharges(final Off "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); } ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0); - - final GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), + GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), chargePaymentDTO.getChargeId()); + final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(), paymentTypeId); if (isReversal) { @@ -841,7 +853,8 @@ private void createCreditJournalEntryForClientPayments(final Office office, fina } private void createCreditJournalEntryForSavings(final Office office, final String currencyCode, final GLAccount account, - final Long savingsId, final String transactionId, final Date transactionDate, final BigDecimal amount) { + final Long savingsId, final String transactionId, final Date transactionDate, final BigDecimal amount) + throws DataAccessException { final boolean manualEntry = false; LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; @@ -857,6 +870,7 @@ private void createCreditJournalEntryForSavings(final Office office, final Strin final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, loanTransaction, savingsAccountTransaction, clientTransaction, shareTransactionId); + this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -947,6 +961,7 @@ private void createDebitJournalEntryForSavings(final Office office, final String final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, loanTransaction, savingsAccountTransaction, clientTransaction, shareTransactionId); + this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -1161,6 +1176,7 @@ private GLAccount getLinkedGLAccountForLoanCharges(final Long loanProductId, fin private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, final Long chargeId) { + ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, PortfolioProductType.SAVING.getValue(), accountMappingTypeId); /***** @@ -1170,15 +1186,22 @@ private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductI *****/ // Vishwas TODO: remove this condition as it should always be true + if (accountMappingTypeId == CashAccountsForSavings.INCOME_FROM_FEES.getValue() || accountMappingTypeId == CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue()) { + GLAccount glAccount = chargeRepositoryWrapper.findOneWithNotFoundDetection(chargeId).getAccount(); + if (glAccount != null) { + return glAccount; + } final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository .findProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(savingsProductId, PortfolioProductType.SAVING.getValue(), accountMappingTypeId, chargeId); if (chargeSpecificIncomeAccountMapping != null) { + accountMapping = chargeSpecificIncomeAccountMapping; } } + return accountMapping.getGlAccount(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java index 054a134dd25..bafdcbdcf3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java @@ -46,4 +46,5 @@ public interface JournalEntryWritePlatformService { void createJournalEntriesForShares(Map accountingBridgeData); void revertShareAccountJournalEntries(ArrayList transactionId, Date transactionDate); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index 0f69a407efd..3649246b1d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -69,7 +70,6 @@ import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; @@ -530,7 +530,6 @@ public void createJournalEntriesForSavings(final Map accountingB .determineProcessor(savingsDTO); accountingProcessorForSavings.createJournalEntriesForSavings(savingsDTO); } - } @Transactional @@ -593,7 +592,7 @@ public void revertShareAccountJournalEntries(final ArrayList transactionId private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) { /** check if date of Journal entry is valid ***/ final LocalDate entryLocalDate = command.getTransactionDate(); - final Date transactionDate = Date.from(entryLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + final Date transactionDate = Date.from(entryLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); // shouldn't be in the future final Date todaysDate = new Date(); if (transactionDate.after(todaysDate)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java index 9dd074e89f8..24cfcf33a61 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -115,8 +115,8 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, final Map changes) { // save both fee and penalty charges - saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true); - saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false); + saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true); + saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false); } public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java index 2db50c55f12..ca2c2f6d150 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java @@ -162,7 +162,7 @@ public void savePaymentChannelToFundSourceMappings(final JsonCommand command, fi * @param productId * @param changes */ - public void saveChargesToIncomeOrLiabilityAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + public void saveChargesToGLAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, final Map changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) { String arrayName; if (isPenalty) { @@ -232,7 +232,7 @@ public void updateChargeToIncomeAccountMappings(final JsonCommand command, final // If input map is empty, delete all existing mappings if (inputChargeToIncomeAccountMap.size() == 0) { - this.accountMappingRepository.deleteInBatch(existingChargeToIncomeAccountMappings); + this.accountMappingRepository.deleteAllInBatch(existingChargeToIncomeAccountMappings); } /** * Else,
* update existing mappings OR
@@ -315,7 +315,7 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, // If input map is empty, delete all existing mappings if (inputPaymentChannelFundSourceMap.size() == 0) { - this.accountMappingRepository.deleteInBatch(existingPaymentChannelToFundSourceMappings); + this.accountMappingRepository.deleteAllInBatch(existingPaymentChannelToFundSourceMappings); } /** * Else,
* update existing mappings OR
@@ -332,8 +332,7 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, if (inputPaymentChannelFundSourceMap.containsKey(currentPaymentChannelId)) { final Long newGLAccountId = inputPaymentChannelFundSourceMap.get(currentPaymentChannelId); if (!newGLAccountId.equals(existingPaymentChannelToFundSourceMapping.getGlAccount().getId())) { - final GLAccount glAccount = getAccountByIdAndType(LoanProductAccountingParams.FUND_SOURCE.getValue(), - GLAccountType.ASSET, newGLAccountId); + final GLAccount glAccount = getAccountById(LoanProductAccountingParams.FUND_SOURCE.getValue(), newGLAccountId); existingPaymentChannelToFundSourceMapping.setGlAccount(glAccount); this.accountMappingRepository.save(existingPaymentChannelToFundSourceMapping); } @@ -362,8 +361,7 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, private void savePaymentChannelToFundSourceMapping(final Long productId, final Long paymentTypeId, final Long paymentTypeSpecificFundAccountId, final PortfolioProductType portfolioProductType) { final PaymentType paymentType = this.paymentTypeRepositoryWrapper.findOneWithNotFoundDetection(paymentTypeId); - final GLAccount glAccount = getAccountByIdAndType(LoanProductAccountingParams.FUND_SOURCE.getValue(), GLAccountType.ASSET, - paymentTypeSpecificFundAccountId); + final GLAccount glAccount = getAccountById(LoanProductAccountingParams.FUND_SOURCE.getValue(), paymentTypeSpecificFundAccountId); final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, portfolioProductType.getValue(), CashAccountsForLoan.FUND_SOURCE.getValue(), paymentType); this.accountMappingRepository.save(accountMapping); @@ -427,6 +425,11 @@ public GLAccount getAccountByIdAndType(final String paramName, final GLAccountTy return glAccount; } + public GLAccount getAccountById(final String paramName, final Long accountId) { + final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId); + return glAccount; + } + public GLAccount getAccountByIdAndType(final String paramName, final List expectedAccountTypes, final Long accountId) { final GLAccount glAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId); // validate account is of the expected Type @@ -445,7 +448,7 @@ public void deleteProductToGLAccountMapping(final Long loanProductId, final Port final List productToGLAccountMappings = this.accountMappingRepository .findByProductIdAndProductType(loanProductId, portfolioProductType.getValue()); if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) { - this.accountMappingRepository.deleteInBatch(productToGLAccountMappings); + this.accountMappingRepository.deleteAllInBatch(productToGLAccountMappings); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java index 3b0a304cc26..47df148a8eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java @@ -29,7 +29,7 @@ public interface ProductToGLAccountMappingReadPlatformService { List fetchPaymentTypeToFundSourceMappingsForLoanProduct(Long loanProductId); - List fetchFeeToIncomeOrLiabilityAccountMappingsForLoanProduct(Long loanProductId); + List fetchFeeToGLAccountMappingsForLoanProduct(Long loanProductId); List fetchPenaltyToIncomeAccountMappingsForLoanProduct(Long loanProductId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index 85718d93f7f..38bfabcdf38 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -279,7 +279,7 @@ private List fetchPaymentTypeToFundSourceMappings( } @Override - public List fetchFeeToIncomeOrLiabilityAccountMappingsForLoanProduct(final Long loanProductId) { + public List fetchFeeToGLAccountMappingsForLoanProduct(final Long loanProductId) { return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, false); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java index 37a4dc8f07f..598f9c311f6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java @@ -121,8 +121,8 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, final Map changes) { // save both fee and penalty charges - saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true); - saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false); + saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true); + saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false); } public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java index 0ec0483309e..51ca34b3d93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java @@ -114,8 +114,8 @@ public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, final Map changes) { - saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, true); - saveChargesToIncomeOrLiabilityAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, false); + saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, true); + saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SHARES, false); } public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java index 05fad4c64c5..46c0902db44 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/domain/LoanProductProvisioningEntry.java @@ -143,4 +143,11 @@ public int hashCode() { return Objects.hash(entry.getId(), criteriaId, office.getId(), currencyCode, loanProduct.getId(), provisioningCategory.getId(), overdueInDays, reservedAmount, liabilityAccount.getId(), expenseAccount.getId()); } + + public int partialHashCode() { + // this is used to group together all the entries that have similar parameters (excluding the amount reserved) + // rather than a check for if the objects are the same based on their values, this tells if they are similar + return Objects.hash(entry.getId(), criteriaId, office.getId(), currencyCode, loanProduct.getId(), provisioningCategory.getId(), + overdueInDays, liabilityAccount.getId(), expenseAccount.getId()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java index d1679c4439a..ba226f1a341 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,7 @@ import com.google.gson.JsonObject; import java.time.LocalDate; +import java.time.ZoneId; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -150,13 +151,13 @@ private boolean isJournalEntriesRequired(JsonCommand command) { private Date parseDate(JsonCommand command) { LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed("date", command.parsedJson()); - return Date.from(localDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); } @Override @CronTarget(jobName = JobName.GENERATE_LOANLOSS_PROVISIONING) public void generateLoanLossProvisioningAmount() { - Date currentDate = Date.from(DateUtils.getLocalDateOfTenant().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + Date currentDate = Date.from(DateUtils.getLocalDateOfTenant().atStartOfDay(ZoneId.systemDefault()).toInstant()); boolean addJournalEntries = true; try { Collection criteriaCollection = this.provisioningCriteriaReadPlatformService @@ -229,7 +230,7 @@ public CommandProcessingResult reCreateProvisioningEntries(Long provisioningEntr private Collection generateLoanProvisioningEntry(ProvisioningEntry parent, Date date) { Collection entries = this.provisioningEntriesReadPlatformService .retrieveLoanProductsProvisioningData(date); - Map provisioningEntries = new HashMap<>(); + Map provisioningEntries = new HashMap<>(); for (LoanProductProvisioningEntryData data : entries) { LoanProduct loanProduct = this.loanProductRepository.findById(data.getProductId()).get(); Office office = this.officeRepositoryWrapper.findOneWithNotFoundDetection(data.getOfficeId()); @@ -244,10 +245,10 @@ private Collection generateLoanProvisioningEntry(P provisioningCategory, data.getOverdueInDays(), amountToReserve.getAmount(), liabilityAccount, expenseAccount, criteraId); entry.setProvisioningEntry(parent); - if (!provisioningEntries.containsKey(entry)) { - provisioningEntries.put(entry, entry); + if (!provisioningEntries.containsKey(entry.partialHashCode())) { + provisioningEntries.put(entry.partialHashCode(), entry); } else { - LoanProductProvisioningEntry entry1 = provisioningEntries.get(entry); + LoanProductProvisioningEntry entry1 = provisioningEntries.get(entry.partialHashCode()); entry1.addReservedAmount(entry.getReservedAmount()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java index 3a7423adbdd..cb16b74351f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/exception/ErrorHandler.java @@ -19,7 +19,6 @@ package org.apache.fineract.batch.exception; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; @@ -32,6 +31,7 @@ import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformInternalServerExceptionMapper; import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformResourceNotFoundExceptionMapper; import org.apache.fineract.infrastructure.core.exceptionmapper.UnsupportedParameterExceptionMapper; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.springframework.dao.NonTransientDataAccessException; @@ -49,7 +49,7 @@ */ public class ErrorHandler extends RuntimeException { - private static Gson jsonHelper = new GsonBuilder().setPrettyPrinting().create(); + private static Gson jsonHelper = GoogleGsonSerializerHelper.createGsonBuilder(true).create(); /** * Sole Constructor diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 29f6dd64a3a..ee6ddc948bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -482,6 +482,14 @@ public CommandWrapperBuilder createCharge() { return this; } + public CommandWrapperBuilder createCollateral() { + this.actionName = "CREATE"; + this.entityId = null; + this.entityName = "COLLATERAL_PRODUCT"; + this.href = "/collateral-product"; + return this; + } + public CommandWrapperBuilder updateCharge(final Long chargeId) { this.actionName = "UPDATE"; this.entityName = "CHARGE"; @@ -870,6 +878,15 @@ public CommandWrapperBuilder loanForeclosure(final Long loanId) { return this; } + public CommandWrapperBuilder undoWaiveChargeTransaction(final Long loanId, final Long transactionId) { + this.actionName = "UNDO"; + this.entityName = "WAIVECHARGE"; + this.entityId = transactionId; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/transactions?command=undo"; + return this; + } + public CommandWrapperBuilder createLoanApplication() { this.actionName = "CREATE"; this.entityName = "LOAN"; @@ -879,6 +896,33 @@ public CommandWrapperBuilder createLoanApplication() { return this; } + public CommandWrapperBuilder updatePostDatedCheck(final Long id, final Long loanId) { + this.actionName = "UPDATE"; + this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS"; + this.entityId = id; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id; + return this; + } + + public CommandWrapperBuilder bouncedCheck(final Long id, final Long loanId) { + this.actionName = "BOUNCE"; + this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS"; + this.entityId = id; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id; + return this; + } + + public CommandWrapperBuilder deletePostDatedCheck(final Long id, final Long loanId) { + this.actionName = "DELETE"; + this.entityName = "REPAYMENT_WITH_POSTDATEDCHECKS"; + this.entityId = id; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/repaymentwithpostdatedchecks/" + id; + return this; + } + public CommandWrapperBuilder updateLoanApplication(final Long loanId) { this.actionName = "UPDATE"; this.entityName = "LOAN"; @@ -1814,6 +1858,32 @@ public CommandWrapperBuilder updateCollateral(final Long loanId, final Long coll return this; } + public CommandWrapperBuilder updateCollateralProduct(final Long collateralId) { + this.actionName = "UPDATE"; + this.entityName = "COLLATERAL_PRODUCT"; + this.entityId = collateralId; + this.href = "/collateral-management/" + collateralId; + return this; + } + + public CommandWrapperBuilder updateClientCollateralProduct(final Long clientId, final Long collateralId) { + this.actionName = "UPDATE"; + this.entityName = "CLIENT_COLLATERAL_PRODUCT"; + this.entityId = collateralId; + this.clientId = clientId; + this.href = "/clients/" + clientId + "/collateral/" + collateralId; + return this; + } + + public CommandWrapperBuilder deleteLoanCollateral(final Long loanId, final Long collateralId) { + this.actionName = "DELETE"; + this.entityName = "LOAN_COLLATERAL_PRODUCT"; + this.entityId = collateralId; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/collateral/" + collateralId; + return this; + } + public CommandWrapperBuilder deleteCollateral(final Long loanId, final Long collateralId) { this.actionName = "DELETE"; this.entityName = "COLLATERAL"; @@ -1823,6 +1893,31 @@ public CommandWrapperBuilder deleteCollateral(final Long loanId, final Long coll return this; } + public CommandWrapperBuilder deleteCollateralProduct(final Long collateralId) { + this.actionName = "DELETE"; + this.entityName = "COLLATERAL_PRODUCT"; + this.entityId = collateralId; + this.href = "/collateral-management/" + collateralId; + return this; + } + + public CommandWrapperBuilder deleteClientCollateralProduct(final Long collateralId, final Long clientId) { + this.actionName = "DELETE"; + this.entityName = "CLIENT_COLLATERAL_PRODUCT"; + this.entityId = collateralId; + this.clientId = clientId; + this.href = "/clients/" + clientId + "/collateral-management/" + collateralId; + return this; + } + + public CommandWrapperBuilder addClientCollateralProduct(final Long clientId) { + this.actionName = "CREATE"; + this.entityName = "CLIENT_COLLATERAL_PRODUCT"; + this.clientId = clientId; + this.href = "/clients/" + clientId + "/collateral-management"; + return this; + } + public CommandWrapperBuilder updateCollectionSheet(final Long groupId) { this.actionName = "UPDATE"; this.entityName = "COLLECTIONSHEET"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index d1e5c0f19ad..22c3df4a9d1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -19,8 +19,9 @@ package org.apache.fineract.commands.service; import com.google.gson.JsonElement; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.security.SecureRandom; import java.time.ZonedDateTime; -import java.util.Random; import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandSourceRepository; import org.apache.fineract.commands.domain.CommandWrapper; @@ -52,6 +53,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio private final CommandProcessingService processAndLogCommandService; private final SchedulerJobRunnerReadService schedulerJobRunnerReadService; private static final Logger LOG = LoggerFactory.getLogger(PortfolioCommandSourceWritePlatformServiceImpl.class); + private static final SecureRandom random = new SecureRandom(); @Autowired public PortfolioCommandSourceWritePlatformServiceImpl(final PlatformSecurityContext context, @@ -66,6 +68,8 @@ public PortfolioCommandSourceWritePlatformServiceImpl(final PlatformSecurityCont @Override @SuppressWarnings("AvoidHidingCauseException") + @SuppressFBWarnings(value = { + "DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once") public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { boolean isApprovedByChecker = false; @@ -106,18 +110,17 @@ public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { if (numberOfRetries >= maxNumberOfRetries) { LOG.warn("The following command {} has been retried for the max allowed attempts of {} and will be rolled back", command.json(), numberOfRetries); - throw (exception); + throw exception; } /*** * Else sleep for a random time (between 1 to 10 seconds) and continue **/ try { - Random random = new Random(); int randomNum = random.nextInt(maxIntervalBetweenRetries + 1); Thread.sleep(1000 + (randomNum * 1000)); numberOfRetries = numberOfRetries + 1; } catch (InterruptedException e) { - throw (exception); + throw exception; } } catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) { numberOfRetries = maxNumberOfRetries + 1; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java index 37393224a14..c04979fa5aa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatData.java @@ -34,12 +34,15 @@ public class AccountNumberFormatData implements Serializable { private List accountTypeOptions; private Map> prefixTypeOptions; - public AccountNumberFormatData(final Long id, final EnumOptionData accountType, final EnumOptionData prefixType) { - this(id, accountType, prefixType, null, null); + private String prefixCharacter; + + public AccountNumberFormatData(final Long id, final EnumOptionData accountType, final EnumOptionData prefixType, + final String prefixCharacter) { + this(id, accountType, prefixType, null, null, prefixCharacter); } public AccountNumberFormatData(final List accountTypeOptions, Map> prefixTypeOptions) { - this(null, null, null, accountTypeOptions, prefixTypeOptions); + this(null, null, null, accountTypeOptions, prefixTypeOptions, null); } public void templateOnTop(List accountTypeOptions, Map> prefixTypeOptions) { @@ -48,12 +51,14 @@ public void templateOnTop(List accountTypeOptions, Map accountTypeOptions, Map> prefixTypeOptions) { + final List accountTypeOptions, Map> prefixTypeOptions, + final String prefixCharacter) { this.id = id; this.accountType = accountType; this.prefixType = prefixType; this.accountTypeOptions = accountTypeOptions; this.prefixTypeOptions = prefixTypeOptions; + this.prefixCharacter = prefixCharacter; } public Long getId() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java index 09c8a42538c..7bdbe2271a7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java @@ -51,10 +51,11 @@ public class AccountNumberFormatDataValidator { private static final Logger LOG = LoggerFactory.getLogger(AccountNumberFormatDataValidator.class); private static final Set ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(AccountNumberFormatConstants.accountTypeParamName, AccountNumberFormatConstants.prefixTypeParamName)); + Arrays.asList(AccountNumberFormatConstants.accountTypeParamName, AccountNumberFormatConstants.prefixTypeParamName, + AccountNumberFormatConstants.prefixCharacterParamName)); private static final Set ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(AccountNumberFormatConstants.prefixTypeParamName)); + Arrays.asList(AccountNumberFormatConstants.prefixTypeParamName, AccountNumberFormatConstants.prefixCharacterParamName)); @Autowired public AccountNumberFormatDataValidator(final FromJsonHelper fromApiJsonHelper) { @@ -84,6 +85,13 @@ public void validateForCreate(final String json) { DataValidatorBuilder dataValidatorForValidatingPrefixType = baseDataValidator.reset() .parameter(AccountNumberFormatConstants.prefixTypeParamName).value(prefixType).notNull().integerGreaterThanZero(); + if (prefixType.equals(AccountNumberPrefixType.PREFIX_SHORT_NAME.getValue())) { + final String prefixCharacter = this.fromApiJsonHelper + .extractStringNamed(AccountNumberFormatConstants.prefixCharacterParamName, element); + + baseDataValidator.reset().parameter(AccountNumberFormatConstants.prefixTypeParamName).value(prefixCharacter).notBlank(); + } + /** * Permitted values for prefix type vary based on the actual selected accountType, carry out this validation * only if data validation errors do not exist for both entity type and prefix type diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java index fa4025383ff..77be4fc6f70 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormat.java @@ -37,21 +37,29 @@ public class AccountNumberFormat extends AbstractPersistableCustom { @Column(name = AccountNumberFormatConstants.PREFIX_TYPE_ENUM_COLUMN_NAME, nullable = true) private Integer prefixEnum; + @Column(name = AccountNumberFormatConstants.PREFIX_CHARACTER_COLUMN_NAME, nullable = true) + private String prefixCharacter; + protected AccountNumberFormat() { // } - public AccountNumberFormat(EntityAccountType entityAccountType, AccountNumberPrefixType prefixType) { + public AccountNumberFormat(EntityAccountType entityAccountType, AccountNumberPrefixType prefixType, String prefixCharacter) { this.accountTypeEnum = entityAccountType.getValue(); if (prefixType != null) { this.prefixEnum = prefixType.getValue(); } + this.prefixCharacter = prefixCharacter; } public Integer getAccountTypeEnum() { return this.accountTypeEnum; } + public String getPrefixCharacter() { + return this.prefixCharacter; + } + public EntityAccountType getAccountType() { return EntityAccountType.fromInt(this.accountTypeEnum); } @@ -75,4 +83,8 @@ private void setPrefixEnum(Integer prefixEnum) { public void setPrefix(AccountNumberPrefixType accountNumberPrefixType) { setPrefixEnum(accountNumberPrefixType.getValue()); } + + public void setPrefixCharacter(String prefixCharacter) { + this.prefixCharacter = prefixCharacter; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java index 37649e23e2d..8e1b2f50cb5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/domain/AccountNumberFormatEnumerations.java @@ -35,13 +35,16 @@ private AccountNumberFormatEnumerations() { } public static final Set accountNumberPrefixesForClientAccounts = Collections - .unmodifiableSet(new HashSet<>(Arrays.asList(AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.CLIENT_TYPE))); + .unmodifiableSet(new HashSet<>(Arrays.asList(AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.CLIENT_TYPE, + AccountNumberPrefixType.PREFIX_SHORT_NAME))); - public static final Set accountNumberPrefixesForLoanAccounts = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList(AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.LOAN_PRODUCT_SHORT_NAME))); + public static final Set accountNumberPrefixesForLoanAccounts = Collections + .unmodifiableSet(new HashSet<>(Arrays.asList(AccountNumberPrefixType.OFFICE_NAME, + AccountNumberPrefixType.LOAN_PRODUCT_SHORT_NAME, AccountNumberPrefixType.PREFIX_SHORT_NAME))); - public static final Set accountNumberPrefixesForSavingsAccounts = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList(AccountNumberPrefixType.OFFICE_NAME, AccountNumberPrefixType.SAVINGS_PRODUCT_SHORT_NAME))); + public static final Set accountNumberPrefixesForSavingsAccounts = Collections + .unmodifiableSet(new HashSet<>(Arrays.asList(AccountNumberPrefixType.OFFICE_NAME, + AccountNumberPrefixType.SAVINGS_PRODUCT_SHORT_NAME, AccountNumberPrefixType.PREFIX_SHORT_NAME))); public static final Set accountNumberPrefixesForCenters = Collections .unmodifiableSet(new HashSet<>(Collections.singletonList(AccountNumberPrefixType.OFFICE_NAME))); @@ -54,7 +57,8 @@ public enum AccountNumberPrefixType { OFFICE_NAME(1, "accountNumberPrefixType.officeName"), CLIENT_TYPE(101, "accountNumberPrefixType.clientType"), LOAN_PRODUCT_SHORT_NAME(201, "accountNumberPrefixType.loanProductShortName"), SAVINGS_PRODUCT_SHORT_NAME(301, - "accountNumberPrefixType.savingsProductShortName"); + "accountNumberPrefixType.savingsProductShortName"), PREFIX_SHORT_NAME(401, + "accountNumberPrefixType.prefixShortName"); private final Integer value; private final String code; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java index 0bce265c055..5800e23619e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java @@ -40,6 +40,7 @@ private AccountNumberFormatConstants() { public static final String idParamName = "id"; public static final String accountTypeParamName = "accountType"; public static final String prefixTypeParamName = "prefixType"; + public static final String prefixCharacterParamName = "prefixCharacter"; // response parameters @@ -62,5 +63,6 @@ private AccountNumberFormatConstants() { public static final String ACCOUNT_TYPE_ENUM_COLUMN_NAME = "account_type_enum"; public static final String PREFIX_TYPE_ENUM_COLUMN_NAME = "prefix_type_enum"; public static final String ACCOUNT_TYPE_UNIQUE_CONSTRAINT_NAME = "account_type_enum"; + public static final String PREFIX_CHARACTER_COLUMN_NAME = "prefix_character"; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java index d25c2911ce7..22a8878082a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java @@ -64,7 +64,8 @@ private static final class AccountNumberFormatMapper implements RowMapper registrationBean = new FilterRegistrationBean( - filter); - registrationBean.setEnabled(false); - return registrationBean; } + + // Column indices + public static final int CHARGE_NAME_COL = 1; + public static final int CHARGE_AMOUNT_COL = 2; + public static final int CHARGE_CALCULATION_TYPE_COL = 3; + // public static final int CHARGE_DUE_DATE_COL = 4; + public static final int CHARGE_TIME_TYPE_COL = 4; + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/ChartOfAcountsConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/ChartOfAcountsConstants.java index 59a9032ed0e..0dd414b3809 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/ChartOfAcountsConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/ChartOfAcountsConstants.java @@ -34,10 +34,22 @@ private ChartOfAcountsConstants() { public static final int TAG_COL = 7;// H public static final int TAG_ID_COL = 8;// I public static final int DESCRIPTION_COL = 9;// J - public static final int LOOKUP_ACCOUNT_TYPE_COL = 15;// P - public static final int LOOKUP_ACCOUNT_NAME_COL = 16; // Q - public static final int LOOKUP_ACCOUNT_ID_COL = 17;// R - public static final int LOOKUP_TAG_COL = 18; // S - public static final int LOOKUP_TAG_ID_COL = 19; // T - public static final int STATUS_COL = 20; + // adding for opening balance bulk import + public static final int OFFICE_COL = 10; // K + public static final int OFFICE_COL_ID = 11; // L + public static final int CURRENCY_CODE = 12; // M + public static final int DEBIT_AMOUNT = 13; // N + public static final int CREDIT_AMOUNT = 14; // O + + public static final int LOOKUP_ACCOUNT_TYPE_COL = 18;// S + public static final int LOOKUP_ACCOUNT_NAME_COL = 19; // T + public static final int LOOKUP_ACCOUNT_ID_COL = 20;// U + public static final int LOOKUP_TAG_COL = 21; // V + public static final int LOOKUP_TAG_ID_COL = 22; // W + + // adding for opening balance bulk import + public static final int LOOKUP_OFFICE_COL = 23; // X + public static final int LOOKUP_OFFICE_ID_COL = 24; // Y + + public static final int STATUS_COL = 25; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/LoanConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/LoanConstants.java index b1996c4ed0d..7d9fa3ce803 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/LoanConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/LoanConstants.java @@ -60,10 +60,11 @@ private LoanConstants() { public static final int LOAN_ID_COL = 33;// AH public static final int FAILURE_REPORT_COL = 34;// AI public static final int EXTERNAL_ID_COL = 35;// AJ - public static final int CHARGE_ID_1 = 36;// AK + public static final int CHARGE_NAME_1 = 36;// AK public static final int CHARGE_AMOUNT_1 = 37;// AL + public static final int CHARGE_DUE_DATE_1 = 38;// AM - public static final int CHARGE_ID_2 = 39;// AN + public static final int CHARGE_NAME_2 = 39;// AO public static final int CHARGE_AMOUNT_2 = 40;// AO public static final int CHARGE_DUE_DATE_2 = 41;// AP public static final int GROUP_ID = 42;// AQ @@ -71,6 +72,10 @@ private LoanConstants() { public static final int LOOKUP_CLIENT_EXTERNAL_ID = 44;// AS public static final int LOOKUP_ACTIVATION_DATE_COL = 45;// AT public static final int LINK_ACCOUNT_ID = 46;// AU + public static final int LOAN_COLLATERAL_ID = 47;// AV + public static final int LOAN_COLLATERAL_QUANTITY = 48;// AW + public static final int CHARGE_AMOUNT_TYPE_1 = 49;// AX + public static final int CHARGE_AMOUNT_TYPE_2 = 50;// AY public static final String LOAN_TYPE_INDIVIDUAL = "Individual"; public static final String LOAN_TYPE_GROUP = "Group"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java index c18abbbe815..a93f674073c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java @@ -35,6 +35,7 @@ private TemplatePopulateImportConstants() { public static final String CENTER_SHEET_NAME = "Centers"; public static final String STAFF_SHEET_NAME = "Staff"; public static final String GROUP_SHEET_NAME = "Groups"; + public static final String CHARGE_SHEET_NAME = "Charges"; public static final String CHART_OF_ACCOUNTS_SHEET_NAME = "ChartOfAccounts"; public static final String CLIENT_ENTITY_SHEET_NAME = "ClientEntity"; public static final String CLIENT_PERSON_SHEET_NAME = "ClientPerson"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java index 4b7bc7ac535..ce3bb80e3f2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/ImportHandlerUtils.java @@ -19,11 +19,11 @@ package org.apache.fineract.infrastructure.bulkimport.importhandler; import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; import java.time.LocalDate; import java.util.List; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformException; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -133,7 +133,7 @@ public static String readAsString(int colIndex, Row row) { public static String trimEmptyDecimalPortion(String result) { if (result != null && result.endsWith(".0")) { - return Iterables.get(Splitter.on("\\.").split(result), 0); + return Splitter.on("\\.").split(result).iterator().next(); } else { return result; } @@ -304,6 +304,7 @@ public static Long getIdByName(Sheet sheet, String name) { if (sheetName.equals(TemplatePopulateImportConstants.OFFICE_SHEET_NAME) || sheetName.equals(TemplatePopulateImportConstants.GL_ACCOUNTS_SHEET_NAME) || sheetName.equals(TemplatePopulateImportConstants.EXTRAS_SHEET_NAME) + || sheetName.equals(TemplatePopulateImportConstants.CHARGE_SHEET_NAME) || sheetName.equals(TemplatePopulateImportConstants.SHARED_PRODUCTS_SHEET_NAME) || sheetName.equals(TemplatePopulateImportConstants.ROLES_SHEET_NAME)) { if (row.getCell(cell.getColumnIndex() - 1).getCellType() == CellType.NUMERIC) { @@ -342,6 +343,48 @@ public static Long getIdByName(Sheet sheet, String name) { return 0L; } + public static EnumOptionData getChargeTimeTypeEmun(Sheet sheet, String name) { + String sheetName = sheet.getSheetName(); + String chargeTimeType = ""; + EnumOptionData chargeTimeTypeEnum = null; + if (sheetName.equals(TemplatePopulateImportConstants.CHARGE_SHEET_NAME)) { + for (Row row : sheet) { + for (Cell cell : row) { + if (name != null) { + if (cell.getCellType() == CellType.STRING && cell.getRichStringCellValue().getString().trim().equals(name)) { + chargeTimeType = row.getCell(cell.getColumnIndex() + 3).getStringCellValue().toString(); + + } + } + } + } + } + if (!chargeTimeType.equals("")) { + String chargeTimeTypeId = ""; + if (chargeTimeType.equalsIgnoreCase("Disbursement")) { + chargeTimeTypeId = "1"; + } + chargeTimeTypeEnum = new EnumOptionData(null, null, chargeTimeTypeId); + } + return chargeTimeTypeEnum; + } + + public static EnumOptionData getChargeAmountTypeEnum(final String chargeAmountType) { + EnumOptionData chargeAmountTypeEnum = null; + if (chargeAmountType != null) { + String chargeAmountTypeId = ""; + if (chargeAmountType.equalsIgnoreCase("Flat")) { + chargeAmountTypeId = "1"; + } else if (chargeAmountType.equalsIgnoreCase("% Amount")) { + chargeAmountTypeId = "2"; + } else { + chargeAmountTypeId = chargeAmountType; + } + chargeAmountTypeEnum = new EnumOptionData(null, null, chargeAmountTypeId); + } + return chargeAmountTypeEnum; + } + public static String getCodeByName(Sheet sheet, String name) { String sheetName = sheet.getSheetName(); if (sheetName.equals(TemplatePopulateImportConstants.EXTRAS_SHEET_NAME)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java index 034381f5afd..c2f0a6a4308 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java @@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.GroupIdSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.group.data.CenterData; import org.apache.fineract.portfolio.group.data.GroupGeneralData; @@ -229,7 +230,7 @@ private int getProgressLevel(String status) { } private CommandProcessingResult importCenter(int rowIndex, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); Type groupCollectionType = new TypeToken>() {}.getType(); gsonBuilder.registerTypeAdapter(groupCollectionType, new GroupIdSerializer()); @@ -254,7 +255,7 @@ private void setReportHeaders(Sheet sheet) { private Integer importCenterMeeting(CommandProcessingResult result, int rowIndex, String dateFormat) { CalendarData calendarData = meetings.get(rowIndex); calendarData.setTitle("centers_" + result.getGroupId().toString() + "_CollectionMeeting"); - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataValueSerializer()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/chartofaccounts/ChartOfAccountsImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/chartofaccounts/ChartOfAccountsImportHandler.java index e58f7cf2817..fa93910869f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/chartofaccounts/ChartOfAccountsImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/chartofaccounts/ChartOfAccountsImportHandler.java @@ -19,11 +19,18 @@ package org.apache.fineract.infrastructure.bulkimport.importhandler.chartofaccounts; import com.google.gson.GsonBuilder; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import org.apache.fineract.accounting.glaccount.data.GLAccountData; +import org.apache.fineract.accounting.glaccount.domain.GLAccount; +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper; import org.apache.fineract.accounting.glaccount.domain.GLAccountType; import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage; +import org.apache.fineract.accounting.journalentry.data.CreditDebit; +import org.apache.fineract.accounting.journalentry.data.JournalEntryData; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; @@ -33,10 +40,14 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandler; import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.CodeValueDataIdSerializer; +import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.CurrencyDateCodeSerializer; +import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOptionDataIdSerializer; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Row; @@ -53,24 +64,40 @@ public class ChartOfAccountsImportHandler implements ImportHandler { private static final Logger LOG = LoggerFactory.getLogger(ChartOfAccountsImportHandler.class); private List glAccounts; private Workbook workbook; + private LocalDate transactionDate; + + // for opening bal + int flagForOpBal = 0; + private List gltransaction; + List credits = new ArrayList<>(); + List debits = new ArrayList<>(); + String locale; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final GLAccountRepositoryWrapper glAccountRepository; @Autowired - public ChartOfAccountsImportHandler(final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + public ChartOfAccountsImportHandler(final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + GLAccountRepositoryWrapper glAccountRepository) { this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.glAccountRepository = glAccountRepository; } @Override public Count process(Workbook workbook, String locale, String dateFormat) { this.glAccounts = new ArrayList<>(); this.workbook = workbook; + // for opening bal + gltransaction = new ArrayList<>(); + credits = new ArrayList<>(); + debits = new ArrayList<>(); + this.locale = locale; + readExcelFile(); - return importEntity(); + return importEntity(dateFormat); } - public void readExcelFile() { - + private void readExcelFile() { Sheet chartOfAccountsSheet = workbook.getSheet(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME); Integer noOfEntries = ImportHandlerUtils.getNumberOfRows(chartOfAccountsSheet, TemplatePopulateImportConstants.FIRST_COLUMN_INDEX); for (int rowIndex = 1; rowIndex <= noOfEntries; rowIndex++) { @@ -83,6 +110,7 @@ public void readExcelFile() { } private GLAccountData readGlAccounts(Row row) { + String accountType = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.ACCOUNT_TYPE_COL, row); EnumOptionData accountTypeEnum = GLAccountType.fromString(accountType); String accountName = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.ACCOUNT_NAME_COL, row); @@ -103,49 +131,143 @@ private GLAccountData readGlAccounts(Row row) { } String glCode = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.GL_CODE_COL, row); Long tagId = null; + CodeValueData tagIdCodeValueData = null; if (ImportHandlerUtils.readAsString(ChartOfAcountsConstants.TAG_ID_COL, row) != null) { tagId = Long.parseLong(ImportHandlerUtils.readAsString(ChartOfAcountsConstants.TAG_ID_COL, row)); + tagIdCodeValueData = new CodeValueData(tagId); } - CodeValueData tagIdCodeValueData = new CodeValueData(tagId); String description = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.DESCRIPTION_COL, row); + if (ImportHandlerUtils.readAsString(ChartOfAcountsConstants.OFFICE_COL, row) != null) { + flagForOpBal = 1; + } else { + flagForOpBal = 0; + } return GLAccountData.importInstance(accountName, parentId, glCode, manualEntriesAllowed, accountTypeEnum, usageEnum, description, tagIdCodeValueData, row.getRowNum()); } - public Count importEntity() { + public Count importEntity(String dateFormat) { Sheet chartOfAccountsSheet = workbook.getSheet(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME); - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataIdSerializer()); gsonBuilder.registerTypeAdapter(CodeValueData.class, new CodeValueDataIdSerializer()); + gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); + gsonBuilder.registerTypeAdapter(CurrencyData.class, new CurrencyDateCodeSerializer()); int successCount = 0; int errorCount = 0; String errorMessage = ""; - for (GLAccountData glAccount : glAccounts) { - try { - String payload = gsonBuilder.create().toJson(glAccount); - final CommandWrapper commandRequest = new CommandWrapperBuilder() // - .createGLAccount() // - .withJson(payload) // - .build(); // - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - successCount++; - Cell statusCell = chartOfAccountsSheet.getRow(glAccount.getRowIndex()).createCell(ChartOfAcountsConstants.STATUS_COL); - statusCell.setCellValue(TemplatePopulateImportConstants.STATUS_CELL_IMPORTED); - statusCell.setCellStyle(ImportHandlerUtils.getCellStyle(workbook, IndexedColors.LIGHT_GREEN)); - } catch (RuntimeException ex) { - errorCount++; - LOG.error("Problem occurred in importEntity function", ex); - errorMessage = ImportHandlerUtils.getErrorMessage(ex); - ImportHandlerUtils.writeErrorMessage(chartOfAccountsSheet, glAccount.getRowIndex(), errorMessage, - ChartOfAcountsConstants.STATUS_COL); + + if (glAccounts != null) { + for (GLAccountData glAccount : glAccounts) { + try { + String payload = gsonBuilder.create().toJson(glAccount); + final CommandWrapper commandRequest = new CommandWrapperBuilder() // + .createGLAccount() // + .withJson(payload) // + .build(); // + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + successCount++; + Cell statusCell = chartOfAccountsSheet.getRow(glAccount.getRowIndex()).createCell(ChartOfAcountsConstants.STATUS_COL); + statusCell.setCellValue(TemplatePopulateImportConstants.STATUS_CELL_IMPORTED); + statusCell.setCellStyle(ImportHandlerUtils.getCellStyle(workbook, IndexedColors.LIGHT_GREEN)); + } catch (RuntimeException ex) { + errorCount++; + LOG.error("Problem occurred in importEntity function", ex); + errorMessage = ImportHandlerUtils.getErrorMessage(ex); + ImportHandlerUtils.writeErrorMessage(chartOfAccountsSheet, glAccount.getRowIndex(), errorMessage, + ChartOfAcountsConstants.STATUS_COL); + } + } + if (flagForOpBal > 0) { + + try { + readExcelFileForOpBal(locale, dateFormat); + JournalEntryData transaction = gltransaction.get(gltransaction.size() - 1); + String payload = gsonBuilder.create().toJson(transaction); + + final CommandWrapper commandRequest = new CommandWrapperBuilder().defineOpeningBalanceForJournalEntry() + .withJson(payload).build(); + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + successCount++; + Cell statusCell = chartOfAccountsSheet.getRow(1).createCell(ChartOfAcountsConstants.STATUS_COL); + statusCell.setCellValue(TemplatePopulateImportConstants.STATUS_CELL_IMPORTED); + statusCell.setCellStyle(ImportHandlerUtils.getCellStyle(workbook, IndexedColors.LIGHT_GREEN)); + } catch (RuntimeException ex) { + errorCount++; + LOG.error("Problem occurred in importEntity function", ex); + errorMessage = ImportHandlerUtils.getErrorMessage(ex); + ImportHandlerUtils.writeErrorMessage(chartOfAccountsSheet, 1, errorMessage, ChartOfAcountsConstants.STATUS_COL); + } } + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.STATUS_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); + ImportHandlerUtils.writeString(ChartOfAcountsConstants.STATUS_COL, + chartOfAccountsSheet.getRow(TemplatePopulateImportConstants.ROWHEADER_INDEX), + TemplatePopulateImportConstants.STATUS_COLUMN_HEADER); + return Count.instance(successCount, errorCount); } + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.STATUS_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); ImportHandlerUtils.writeString(ChartOfAcountsConstants.STATUS_COL, chartOfAccountsSheet.getRow(TemplatePopulateImportConstants.ROWHEADER_INDEX), TemplatePopulateImportConstants.STATUS_COLUMN_HEADER); return Count.instance(successCount, errorCount); + } + // for opening balance + public void readExcelFileForOpBal(final String locale, final String dateFormat) { + + Sheet chartOfAccountsSheet = workbook.getSheet(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME); + Integer noOfEntries = ImportHandlerUtils.getNumberOfRows(chartOfAccountsSheet, TemplatePopulateImportConstants.FIRST_COLUMN_INDEX); + for (int rowIndex = 1; rowIndex <= noOfEntries; rowIndex++) { + Row row; + row = chartOfAccountsSheet.getRow(rowIndex); + + // + JournalEntryData journalEntry = null; + journalEntry = readAddJournalEntries(row, locale, dateFormat); + gltransaction.add(journalEntry); + } + + } + + // for opening balance + private JournalEntryData readAddJournalEntries(Row row, String locale, String dateFormat) { + LocalDate transactionDateCheck = LocalDate.now(ZoneId.systemDefault()); + if (transactionDateCheck != null) { + transactionDate = transactionDateCheck; + } + + String officeName = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.OFFICE_COL, row); + Long officeId = ImportHandlerUtils.readAsLong(ChartOfAcountsConstants.OFFICE_COL_ID, row); + + String currencyCode = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.CURRENCY_CODE, row); + String accountToBeDebitedCredited = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.ACCOUNT_NAME_COL, row); + String glCode = ImportHandlerUtils.readAsString(ChartOfAcountsConstants.GL_CODE_COL, row); + GLAccount glAccount = this.glAccountRepository.findOneByGlCodeWithNotFoundDetection(glCode); + Long glAccountIdToDebitedCredited = glAccount.getId(); + if (glAccountIdToDebitedCredited == null) { + throw new RuntimeException("Account does not exist"); + } + + // String credit = + // readAsString(JournalEntryConstants.GL_ACCOUNT_ID_CREDIT_COL, row); + // String debit = + // readAsString(JournalEntryConstants.GL_ACCOUNT_ID_DEBIT_COL, row); + + if (accountToBeDebitedCredited != null) { + if (ImportHandlerUtils.readAsLong(ChartOfAcountsConstants.CREDIT_AMOUNT, row) != null) { + credits.add(new CreditDebit(glAccountIdToDebitedCredited, + BigDecimal.valueOf(ImportHandlerUtils.readAsLong(ChartOfAcountsConstants.CREDIT_AMOUNT, row)))); + + } else if (ImportHandlerUtils.readAsLong(ChartOfAcountsConstants.DEBIT_AMOUNT, row) != null) { + debits.add(new CreditDebit(glAccountIdToDebitedCredited, + BigDecimal.valueOf(ImportHandlerUtils.readAsLong(ChartOfAcountsConstants.DEBIT_AMOUNT, row)))); + } + } + + return JournalEntryData.importInstance1(officeId, transactionDate, currencyCode, credits, debits, locale, dateFormat); + + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientEntityImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientEntityImportHandler.java index 9b6926bfae9..2a231c10de0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientEntityImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientEntityImportHandler.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.address.data.AddressData; import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.client.data.ClientNonPersonData; @@ -198,7 +199,7 @@ public Count importEntity(String dateFormat) { int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); for (ClientData client : clients) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientPersonImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientPersonImportHandler.java index 42d06f41dfa..4f7f04a8945 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientPersonImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/client/ClientPersonImportHandler.java @@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandler; import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.address.data.AddressData; import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.poi.ss.usermodel.Cell; @@ -183,7 +184,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); for (ClientData client : clients) { try { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositImportHandler.java index a207a9cd606..744904eef01 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositImportHandler.java @@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOptionDataIdSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.savings.data.ClosingOfSavingsAccounts; import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; @@ -344,7 +345,7 @@ private void writeFixedDepositErrorMessage(Long savingsId, String errorMessage, private int importSavingsClosing(Long savingsId, int i, String dateFormat) { if (closedOnDate.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(closedOnDate.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -357,7 +358,7 @@ private int importSavingsClosing(Long savingsId, int i, String dateFormat) { } private CommandProcessingResult importSavings(int i, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataIdSerializer()); JsonObject savingsJsonob = gsonBuilder.create().toJsonTree(savings.get(i)).getAsJsonObject(); @@ -382,7 +383,7 @@ private CommandProcessingResult importSavings(int i, String dateFormat) { private int importSavingsApproval(Long savingsId, int i, String dateFormat) { if (approvalDates.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(approvalDates.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -396,7 +397,7 @@ private int importSavingsApproval(Long savingsId, int i, String dateFormat) { private int importSavingsActivation(Long savingsId, int i, String dateFormat) { if (activationDates.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(activationDates.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositTransactionImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositTransactionImportHandler.java index 2086a7e9805..b30df7d41ad 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositTransactionImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/fixeddeposits/FixedDepositTransactionImportHandler.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.SavingsAccountTransactionEnumValueSerialiser; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.poi.ss.usermodel.Cell; @@ -119,7 +120,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(SavingsAccountTransactionEnumData.class, new SavingsAccountTransactionEnumValueSerialiser()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java index 0f321fbf6c9..3d166ee81fa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java @@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOptionDataValueSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.group.data.GroupGeneralData; @@ -233,7 +234,7 @@ private void setReportHeaders(Sheet groupSheet) { private Integer importGroupMeeting(CommandProcessingResult result, int rowIndex, String dateFormat) { CalendarData calendarData = meetings.get(rowIndex); calendarData.setTitle("group_" + result.getGroupId().toString() + "_CollectionMeeting"); - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataValueSerializer()); @@ -250,7 +251,7 @@ private Integer importGroupMeeting(CommandProcessingResult result, int rowIndex, } private CommandProcessingResult importGroup(int rowIndex, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); Type clientCollectionType = new TypeToken>() {}.getType(); gsonBuilder.registerTypeAdapter(clientCollectionType, new ClientIdSerializer()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java index 498f702693b..a14dfbf4fa6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; @@ -125,7 +126,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); for (GuarantorData guarantor : guarantors) { try { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/journalentry/JournalEntriesImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/journalentry/JournalEntriesImportHandler.java index f03fea2af84..74a1d890462 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/journalentry/JournalEntriesImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/journalentry/JournalEntriesImportHandler.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.CurrencyDateCodeSerializer; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; @@ -197,7 +198,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(CurrencyData.class, new CurrencyDateCodeSerializer()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java index 2525331d7ba..17c544b09f1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java @@ -39,11 +39,15 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOptionDataValueSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; +import org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollateralQuantity; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Row; @@ -172,19 +176,21 @@ private LoanAccountData readLoan(Row row, String locale, String dateFormat) { repaidEveryFrequencyId = "1"; } else if (repaidEveryFrequency.equalsIgnoreCase("Months")) { repaidEveryFrequencyId = "2"; + } else if (repaidEveryFrequency.equalsIgnoreCase("Semi Month")) { + repaidEveryFrequencyId = "5"; } repaidEveryFrequencyEnums = new EnumOptionData(null, null, repaidEveryFrequencyId); } Integer loanTerm = ImportHandlerUtils.readAsInt(LoanConstants.LOAN_TERM_COL, row); - String loanTermFrequency = ImportHandlerUtils.readAsString(LoanConstants.LOAN_TERM_FREQUENCY_COL, row); + String loanTermFrequencyType = ImportHandlerUtils.readAsString(LoanConstants.LOAN_TERM_FREQUENCY_COL, row); EnumOptionData loanTermFrequencyEnum = null; - if (loanTermFrequency != null) { + if (loanTermFrequencyType != null) { String loanTermFrequencyId = ""; - if (loanTermFrequency.equalsIgnoreCase("Days")) { + if (loanTermFrequencyType.equalsIgnoreCase("Days")) { loanTermFrequencyId = "0"; - } else if (loanTermFrequency.equalsIgnoreCase("Weeks")) { + } else if (loanTermFrequencyType.equalsIgnoreCase("Weeks")) { loanTermFrequencyId = "1"; - } else if (loanTermFrequency.equalsIgnoreCase("Months")) { + } else if (loanTermFrequencyType.equalsIgnoreCase("Months")) { loanTermFrequencyId = "2"; } loanTermFrequencyEnum = new EnumOptionData(null, null, loanTermFrequencyId); @@ -267,35 +273,84 @@ private LoanAccountData readLoan(Row row, String locale, String dateFormat) { List charges = new ArrayList<>(); - Long charge1 = ImportHandlerUtils.readAsLong(LoanConstants.CHARGE_ID_1, row); - Long charge2 = ImportHandlerUtils.readAsLong(LoanConstants.CHARGE_ID_2, row); + String chargeOneName = ImportHandlerUtils.readAsString(LoanConstants.CHARGE_NAME_1, row); + String chargeTwoName = ImportHandlerUtils.readAsString(LoanConstants.CHARGE_NAME_2, row); + + Long chargeOneId = null; + if (chargeOneName != null) { + chargeOneId = ImportHandlerUtils.getIdByName(workbook.getSheet(TemplatePopulateImportConstants.CHARGE_SHEET_NAME), + chargeOneName); + } + Long chargeTwoId = null; + if (chargeTwoName != null) { + chargeTwoId = ImportHandlerUtils.getIdByName(workbook.getSheet(TemplatePopulateImportConstants.CHARGE_SHEET_NAME), + chargeTwoName); + } + + Long collateralId = ImportHandlerUtils.readAsLong(LoanConstants.LOAN_COLLATERAL_ID, row); Long groupId = ImportHandlerUtils.readAsLong(LoanConstants.GROUP_ID, row); String linkAccountId = ImportHandlerUtils.readAsString(LoanConstants.LINK_ACCOUNT_ID, row); - if (charge1 != null) { + if (chargeOneId != null) { if (ImportHandlerUtils.readAsDouble(LoanConstants.CHARGE_AMOUNT_1, row) != null) { - charges.add(new LoanChargeData(ImportHandlerUtils.readAsLong(LoanConstants.CHARGE_ID_1, row), - ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_1, row), - BigDecimal.valueOf(ImportHandlerUtils.readAsDouble(LoanConstants.CHARGE_AMOUNT_1, row)))); + EnumOptionData chargeOneTimeTypeEnum = ImportHandlerUtils + .getChargeTimeTypeEmun(workbook.getSheet(TemplatePopulateImportConstants.CHARGE_SHEET_NAME), chargeOneName); + EnumOptionData chargeOneAmountTypeEnum = ImportHandlerUtils + .getChargeAmountTypeEnum(ImportHandlerUtils.readAsString(LoanConstants.CHARGE_AMOUNT_TYPE_1, row)); + + BigDecimal chargeAmount; + BigDecimal amountOrPercentage = BigDecimal.valueOf(ImportHandlerUtils.readAsDouble(LoanConstants.CHARGE_AMOUNT_1, row)); + if (chargeOneAmountTypeEnum.getValue().equalsIgnoreCase("1")) { + chargeAmount = amountOrPercentage; + } else { + chargeAmount = LoanCharge.percentageOf(principal, amountOrPercentage); + } + + charges.add(new LoanChargeData(chargeOneId, ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_1, row), + chargeAmount, chargeOneAmountTypeEnum, chargeOneTimeTypeEnum)); } else { - charges.add(new LoanChargeData(ImportHandlerUtils.readAsLong(LoanConstants.CHARGE_ID_1, row), - ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_1, row), null)); + charges.add(new LoanChargeData(chargeOneId, ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_1, row), null)); } } - if (charge2 != null) { + if (chargeTwoId != null) { if (ImportHandlerUtils.readAsDouble(LoanConstants.CHARGE_AMOUNT_2, row) != null) { - charges.add(new LoanChargeData(ImportHandlerUtils.readAsLong(LoanConstants.CHARGE_ID_2, row), - ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_2, row), - BigDecimal.valueOf(ImportHandlerUtils.readAsDouble(LoanConstants.CHARGE_AMOUNT_2, row)))); + EnumOptionData chargeTwoTimeTypeEnum = ImportHandlerUtils + .getChargeTimeTypeEmun(workbook.getSheet(TemplatePopulateImportConstants.CHARGE_SHEET_NAME), chargeTwoName); + EnumOptionData chargeTwoAmountTypeEnum = ImportHandlerUtils + .getChargeAmountTypeEnum(ImportHandlerUtils.readAsString(LoanConstants.CHARGE_AMOUNT_TYPE_2, row)); + + BigDecimal chargeAmount; + BigDecimal amountOrPercentage = BigDecimal.valueOf(ImportHandlerUtils.readAsDouble(LoanConstants.CHARGE_AMOUNT_2, row)); + if (chargeTwoTimeTypeEnum.getValue().equalsIgnoreCase("1")) { + chargeAmount = amountOrPercentage; + } else { + chargeAmount = LoanCharge.percentageOf(principal, amountOrPercentage); + } + + charges.add(new LoanChargeData(chargeTwoId, ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_2, row), + chargeAmount, chargeTwoAmountTypeEnum, chargeTwoTimeTypeEnum)); + } else { + charges.add(new LoanChargeData(chargeTwoId, ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_2, row), null)); + } + } + + List loanCollateralManagementData = new ArrayList<>(); + + if (collateralId != null) { + if (ImportHandlerUtils.readAsDouble(LoanConstants.LOAN_COLLATERAL_QUANTITY, row) != null) { + loanCollateralManagementData.add(new LoanCollateralManagementData(collateralId, + BigDecimal.valueOf(ImportHandlerUtils.readAsDouble(LoanConstants.LOAN_COLLATERAL_QUANTITY, row)), null, null, + null)); } else { - charges.add(new LoanChargeData(ImportHandlerUtils.readAsLong(LoanConstants.CHARGE_ID_2, row), - ImportHandlerUtils.readAsDate(LoanConstants.CHARGE_DUE_DATE_2, row), null)); + throw new InvalidAmountOfCollateralQuantity(null); } } + statuses.add(status); + if (loanType != null) { if (loanType.equals("individual")) { Long clientId = ImportHandlerUtils.getIdByName(workbook.getSheet(TemplatePopulateImportConstants.CLIENT_SHEET_NAME), @@ -305,7 +360,7 @@ private LoanAccountData readLoan(Row row, String locale, String dateFormat) { nominalInterestRate, submittedOnDate, amortizationEnumOption, interestMethodEnum, interestCalculationPeriodEnum, arrearsTolerance, repaymentStrategyId, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, firstRepaymentOnDate, row.getRowNum(), externalId, null, charges, linkAccountId, locale, - dateFormat); + dateFormat, loanCollateralManagementData); } else if (loanType.equals("jlg")) { Long clientId = ImportHandlerUtils.getIdByName(workbook.getSheet(TemplatePopulateImportConstants.CLIENT_SHEET_NAME), clientOrGroupName); @@ -314,7 +369,7 @@ private LoanAccountData readLoan(Row row, String locale, String dateFormat) { nominalInterestRate, submittedOnDate, amortizationEnumOption, interestMethodEnum, interestCalculationPeriodEnum, arrearsTolerance, repaymentStrategyId, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, firstRepaymentOnDate, row.getRowNum(), externalId, groupId, charges, linkAccountId, locale, - dateFormat); + dateFormat, null); } else { Long groupIdforGroupLoan = ImportHandlerUtils .getIdByName(workbook.getSheet(TemplatePopulateImportConstants.GROUP_SHEET_NAME), clientOrGroupName); @@ -412,10 +467,11 @@ private void setReportHeaders(Sheet sheet) { } private Integer importLoanRepayment(CommandProcessingResult result, int rowIndex, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject(); loanRepaymentJsonob.remove("manuallyReversed"); + loanRepaymentJsonob.remove("numberOfRepayments"); String payload = loanRepaymentJsonob.toString(); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .loanRepaymentTransaction(result.getLoanId()) // @@ -431,7 +487,7 @@ private Integer importDisbursalData(CommandProcessingResult result, int rowIndex DisbursementData disbusalData = disbursalDates.get(rowIndex); String linkAccountId = disbusalData.getLinkAccountId(); - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); if (linkAccountId != null && !"".equals(linkAccountId)) { String payload = gsonBuilder.create().toJson(disbusalData); @@ -455,7 +511,7 @@ private Integer importDisbursalData(CommandProcessingResult result, int rowIndex private Integer importLoanApproval(CommandProcessingResult result, int rowIndex, String dateFormat) { if (approvalDates.get(rowIndex) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(approvalDates.get(rowIndex)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -469,7 +525,7 @@ private Integer importLoanApproval(CommandProcessingResult result, int rowIndex, } private CommandProcessingResult importLoan(int rowIndex, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataValueSerializer()); JsonObject loanJsonOb = gsonBuilder.create().toJsonTree(loans.get(rowIndex)).getAsJsonObject(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loanrepayment/LoanRepaymentImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loanrepayment/LoanRepaymentImportHandler.java index 42dd1be3f7f..c3ffe41827f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loanrepayment/LoanRepaymentImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loanrepayment/LoanRepaymentImportHandler.java @@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandler; import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.poi.ss.usermodel.Cell; @@ -112,7 +113,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); for (LoanTransactionData loanRepayment : loanRepayments) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/office/OfficeImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/office/OfficeImportHandler.java index 99c8d3e6cc7..a7fc9f983a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/office/OfficeImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/office/OfficeImportHandler.java @@ -32,6 +32,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.organisation.office.data.OfficeData; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; @@ -88,7 +89,7 @@ private OfficeData readOffice(Row row, final String locale, final String dateFor public Count importEntity(String dateFormat) { Sheet officeSheet = workbook.getSheet(TemplatePopulateImportConstants.OFFICE_SHEET_NAME); - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); int successCount = 0; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositImportHandler.java index a80b80ad800..0ba328371d6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositImportHandler.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOptionDataIdSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.data.SavingsActivation; @@ -346,7 +347,7 @@ private void setReportHeaders(Sheet savingsSheet) { private int importSavingsActivation(Long savingsId, int i, String dateFormat) { if (activationDates.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(activationDates.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -360,7 +361,7 @@ private int importSavingsActivation(Long savingsId, int i, String dateFormat) { private int importSavingsApproval(Long savingsId, int i, String dateFormat) { if (approvalDates.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(approvalDates.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -373,7 +374,7 @@ private int importSavingsApproval(Long savingsId, int i, String dateFormat) { } private CommandProcessingResult importSavings(int i, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataIdSerializer()); JsonObject savingsJsonob = gsonBuilder.create().toJsonTree(savings.get(i)).getAsJsonObject(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositTransactionImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositTransactionImportHandler.java index d8693635a75..3638dddc852 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositTransactionImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/recurringdeposit/RecurringDepositTransactionImportHandler.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.SavingsAccountTransactionEnumValueSerialiser; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.poi.ss.usermodel.Cell; @@ -120,7 +121,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(SavingsAccountTransactionEnumData.class, new SavingsAccountTransactionEnumValueSerialiser()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java index 1c7f46f3f00..f5cda1152f2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsImportHandler.java @@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOptionDataIdSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.data.SavingsActivation; @@ -341,7 +342,7 @@ private void setReportHeaders(Sheet savingsSheet) { private int importSavingsActivation(Long savingsId, int i, String dateFormat) { if (activationDates.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(activationDates.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -355,7 +356,7 @@ private int importSavingsActivation(Long savingsId, int i, String dateFormat) { private int importSavingsApproval(Long savingsId, int i, String dateFormat) { if (approvalDates.get(i) != null) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); String payload = gsonBuilder.create().toJson(approvalDates.get(i)); final CommandWrapper commandRequest = new CommandWrapperBuilder() // @@ -368,7 +369,7 @@ private int importSavingsApproval(Long savingsId, int i, String dateFormat) { } private CommandProcessingResult importSavings(int i, String dateFormat) { - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(EnumOptionData.class, new EnumOptionDataIdSerializer()); JsonObject savingsJsonob = gsonBuilder.create().toJsonTree(savings.get(i)).getAsJsonObject(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsTransactionImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsTransactionImportHandler.java index f581e44c3f1..6940096afeb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsTransactionImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/savings/SavingsTransactionImportHandler.java @@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.SavingsAccountTransactionEnumValueSerialiser; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.poi.ss.usermodel.Cell; @@ -119,7 +120,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); gsonBuilder.registerTypeAdapter(SavingsAccountTransactionEnumData.class, new SavingsAccountTransactionEnumValueSerialiser()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/sharedaccount/SharedAccountImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/sharedaccount/SharedAccountImportHandler.java index 701ece7230d..38b5a619edb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/sharedaccount/SharedAccountImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/sharedaccount/SharedAccountImportHandler.java @@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountChargeData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountData; import org.apache.poi.ss.usermodel.Cell; @@ -153,7 +154,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); for (ShareAccountData shareAccountData : shareAccountDataList) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/staff/StaffImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/staff/StaffImportHandler.java index f0ce9d3b359..6349a3a9235 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/staff/StaffImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/staff/StaffImportHandler.java @@ -32,6 +32,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.DateSerializer; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; @@ -101,7 +102,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); gsonBuilder.registerTypeAdapter(LocalDate.class, new DateSerializer(dateFormat)); for (StaffData staff : staffList) { try { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/users/UserImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/users/UserImportHandler.java index f8e29e173f7..896bebc2d53 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/users/UserImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/users/UserImportHandler.java @@ -31,6 +31,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandler; import org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandlerUtils; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.useradministration.data.AppUserData; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.IndexedColors; @@ -113,7 +114,7 @@ public Count importEntity(String dateFormat) { int successCount = 0; int errorCount = 0; String errorMessage = ""; - GsonBuilder gsonBuilder = new GsonBuilder(); + GsonBuilder gsonBuilder = GoogleGsonSerializerHelper.createGsonBuilder(); for (AppUserData user : users) { try { JsonObject userJsonob = gsonBuilder.create().toJsonTree(user).getAsJsonObject(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/ChargeSheetPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/ChargeSheetPopulator.java new file mode 100644 index 00000000000..f3ef2164c0f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/ChargeSheetPopulator.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.infrastructure.bulkimport.populator; + +import java.util.List; +import org.apache.fineract.infrastructure.bulkimport.constants.ChargeConstants; +import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; +import org.apache.fineract.portfolio.charge.data.ChargeData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +public class ChargeSheetPopulator extends AbstractWorkbookPopulator { + + private List charges; + + private static final int ID_COL = 0; + + public ChargeSheetPopulator(final List charges) { + this.charges = charges; + } + + @Override + public void populate(final Workbook workbook, String dateFormat) { + int rowIndex = 1; + Sheet chargeSheet = workbook.createSheet(TemplatePopulateImportConstants.CHARGE_SHEET_NAME); + setLayout(chargeSheet); + + populateCharges(chargeSheet, rowIndex); + chargeSheet.protectSheet(""); + } + + private void populateCharges(Sheet chargeSheet, int rowIndex) { + for (ChargeData charge : charges) { + Row row = chargeSheet.createRow(rowIndex); + writeLong(ID_COL, row, charge.getId()); + writeString(ChargeConstants.CHARGE_NAME_COL, row, charge.getName().trim().replaceAll("[ )(]", "_")); + writeBigDecimal(ChargeConstants.CHARGE_AMOUNT_COL, row, charge.getAmount()); + writeString(ChargeConstants.CHARGE_CALCULATION_TYPE_COL, row, charge.getChargeCalculationType().getValue()); + writeString(ChargeConstants.CHARGE_TIME_TYPE_COL, row, charge.getChargeTimeType().getValue()); + rowIndex++; + } + } + + private void setLayout(Sheet worksheet) { + worksheet.setColumnWidth(ID_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_NAME_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_AMOUNT_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_CALCULATION_TYPE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_TIME_TYPE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + + Row rowHeader = worksheet.createRow(TemplatePopulateImportConstants.ROWHEADER_INDEX); + rowHeader.setHeight(TemplatePopulateImportConstants.ROW_HEADER_HEIGHT); + + writeString(ID_COL, rowHeader, "ID"); + writeString(ChargeConstants.CHARGE_NAME_COL, rowHeader, "Name"); + writeString(ChargeConstants.CHARGE_AMOUNT_COL, rowHeader, "Charge Amount"); + writeString(ChargeConstants.CHARGE_CALCULATION_TYPE_COL, rowHeader, "Charge Calculation Type"); + writeString(ChargeConstants.CHARGE_TIME_TYPE_COL, rowHeader, "Charge Time Type"); + } + + public Integer getChargesSize() { + return charges.size(); + } + + public List getCharges() { + return charges; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/LoanProductSheetPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/LoanProductSheetPopulator.java index 894fe4ab235..465fb29828a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/LoanProductSheetPopulator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/LoanProductSheetPopulator.java @@ -86,7 +86,12 @@ public void populate(Workbook workbook, String dateFormat) { } else { writeInt(MAX_PRINCIPAL_COL, row, 999999999); } - writeInt(NO_OF_REPAYMENTS_COL, row, product.getNumberOfRepayments()); + int numberOfRepayments = product.getNumberOfRepayments(); + if (product.getRepaymentFrequencyType().getValue().equalsIgnoreCase("Semi Month")) { + numberOfRepayments = numberOfRepayments * product.getRepaymentEvery(); + } + + writeInt(NO_OF_REPAYMENTS_COL, row, numberOfRepayments); if (product.getMinNumberOfRepayments() != null) { writeInt(MIN_REPAYMENTS_COL, row, product.getMinNumberOfRepayments()); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/charge/ChargeWorkbookPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/charge/ChargeWorkbookPopulator.java new file mode 100644 index 00000000000..53fe9155e22 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/charge/ChargeWorkbookPopulator.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.bulkimport.populator.charge; + +import org.apache.fineract.infrastructure.bulkimport.constants.ChargeConstants; +import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; +import org.apache.fineract.infrastructure.bulkimport.populator.AbstractWorkbookPopulator; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +public class ChargeWorkbookPopulator extends AbstractWorkbookPopulator { + + public ChargeWorkbookPopulator() { + // + } + + @Override + public void populate(Workbook workbook, String dateFormat) { + Sheet chargeSheet = workbook.createSheet(TemplatePopulateImportConstants.CHARGE_SHEET_NAME); + setLayout(chargeSheet); + // setRules(chargeSheet, dateFormat); + } + + private void setLayout(final Sheet worksheet) { + Row rowHeader = worksheet.createRow(TemplatePopulateImportConstants.ROWHEADER_INDEX); + worksheet.setColumnWidth(0, TemplatePopulateImportConstants.SMALL_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_NAME_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_AMOUNT_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_CALCULATION_TYPE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + // worksheet.setColumnWidth(ChargeConstants.CHARGE_DUE_DATE_COL, + // TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(ChargeConstants.CHARGE_TIME_TYPE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + + writeString(0, rowHeader, "ID"); + writeString(ChargeConstants.CHARGE_NAME_COL, rowHeader, "Charge Name*"); + writeString(ChargeConstants.CHARGE_AMOUNT_COL, rowHeader, "Charge Amount*"); + writeString(ChargeConstants.CHARGE_CALCULATION_TYPE_COL, rowHeader, "Charge Calculation Type*"); + // writeString(ChargeConstants.CHARGE_DUE_DATE_COL, rowHeader, "Charge Due Date*"); + writeString(ChargeConstants.CHARGE_TIME_TYPE_COL, rowHeader, "Charge Time Type*"); + } + + @SuppressWarnings("unused") + private void setRules(Sheet workSheet, final String dateFormat) { + // CellRangeAddressList dueDateRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + // ChargeConstants.CHARGE_DUE_DATE_COL, ChargeConstants.CHARGE_DUE_DATE_COL); + + // DataValidationHelper validationHelper = new HSSFDataValidationHelper((HSSFSheet) workSheet); + + // DataValidationConstraint dueDateConstraint = validationHelper + // .createDateConstraint(DataValidationConstraint.OperatorType.GREATER_OR_EQUAL, "=TODAY()", null, dateFormat); + + // DataValidation dueDateValidation = validationHelper.createValidation(dueDateConstraint, dueDateRange); + + // workSheet.addValidationData(dueDateValidation); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/chartofaccounts/ChartOfAccountsWorkbook.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/chartofaccounts/ChartOfAccountsWorkbook.java index afc61e7640d..5f961763ba9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/chartofaccounts/ChartOfAccountsWorkbook.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/chartofaccounts/ChartOfAccountsWorkbook.java @@ -29,6 +29,8 @@ import org.apache.fineract.infrastructure.bulkimport.constants.ChartOfAcountsConstants; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; import org.apache.fineract.infrastructure.bulkimport.populator.AbstractWorkbookPopulator; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.office.data.OfficeData; import org.apache.poi.hssf.usermodel.HSSFDataValidationHelper; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.SpreadsheetVersion; @@ -47,12 +49,16 @@ public class ChartOfAccountsWorkbook extends AbstractWorkbookPopulator { private static final Logger LOG = LoggerFactory.getLogger(ChartOfAccountsWorkbook.class); private final List glAccounts; + private final List offices; // adding opening balance office tag + private List currencies; // adding opening balance currency code private Map> accountTypeToAccountNameAndTag; private Map accountTypeToBeginEndIndexesofAccountNames; private List accountTypesNoDuplicatesList; - public ChartOfAccountsWorkbook(List glAccounts) { + public ChartOfAccountsWorkbook(List glAccounts, List offices, List currencies) { this.glAccounts = glAccounts; + this.offices = offices; // opening balance offices names + this.currencies = currencies; // opening balance currency codes } @Override @@ -63,6 +69,7 @@ public void populate(Workbook workbook, String dateFormat) { setLookupTable(chartOfAccountsSheet); setRules(chartOfAccountsSheet); setDefaults(chartOfAccountsSheet); + } private void setAccountTypeToAccountNameAndTag() { @@ -84,6 +91,16 @@ private void addToaccountTypeToAccountNameMap(String key, String value) { } } + private String[] getCurrency() { + String[] currencyCode = new String[currencies.size()]; + int currencyIndex = 0; + for (CurrencyData currencies : currencies) { + currencyCode[currencyIndex] = currencies.code(); + currencyIndex++; + } + return currencyCode; + } + private void setRules(Sheet chartOfAccountsSheet) { CellRangeAddressList accountTypeRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), ChartOfAcountsConstants.ACCOUNT_TYPE_COL, ChartOfAcountsConstants.ACCOUNT_TYPE_COL); @@ -95,9 +112,16 @@ private void setRules(Sheet chartOfAccountsSheet) { ChartOfAcountsConstants.PARENT_COL, ChartOfAcountsConstants.PARENT_COL); CellRangeAddressList tagRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), ChartOfAcountsConstants.TAG_COL, ChartOfAcountsConstants.TAG_COL); + CellRangeAddressList officeNameRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + ChartOfAcountsConstants.OFFICE_COL, ChartOfAcountsConstants.OFFICE_COL); // validation for opening bal + // office column + CellRangeAddressList currencyCodeRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + ChartOfAcountsConstants.CURRENCY_CODE, ChartOfAcountsConstants.CURRENCY_CODE);// validation for currency + // code for opening + // balance DataValidationHelper validationHelper = new HSSFDataValidationHelper((HSSFSheet) chartOfAccountsSheet); - setNames(chartOfAccountsSheet, accountTypesNoDuplicatesList); + setNames(chartOfAccountsSheet, accountTypesNoDuplicatesList, offices); DataValidationConstraint accountTypeConstraint = validationHelper .createExplicitListConstraint(new String[] { GLAccountType.ASSET.toString(), GLAccountType.LIABILITY.toString(), @@ -108,38 +132,48 @@ private void setRules(Sheet chartOfAccountsSheet) { DataValidationConstraint parentConstraint = validationHelper .createFormulaListConstraint("INDIRECT(CONCATENATE(\"AccountName_\",$A1))"); DataValidationConstraint tagConstraint = validationHelper.createFormulaListConstraint("INDIRECT(CONCATENATE(\"Tags_\",$A1))"); + DataValidationConstraint officeNameConstraint = validationHelper.createFormulaListConstraint("Office"); + DataValidationConstraint currencyCodeConstraint = validationHelper.createExplicitListConstraint(getCurrency()); DataValidation accountTypeValidation = validationHelper.createValidation(accountTypeConstraint, accountTypeRange); DataValidation accountUsageValidation = validationHelper.createValidation(accountUsageConstraint, accountUsageRange); DataValidation manualEntriesValidation = validationHelper.createValidation(booleanConstraint, manualEntriesAllowedRange); DataValidation parentValidation = validationHelper.createValidation(parentConstraint, parentRange); DataValidation tagValidation = validationHelper.createValidation(tagConstraint, tagRange); + DataValidation officeNameValidation = validationHelper.createValidation(officeNameConstraint, officeNameRange); + DataValidation currencyCodeValidation = validationHelper.createValidation(currencyCodeConstraint, currencyCodeRange); chartOfAccountsSheet.addValidationData(accountTypeValidation); chartOfAccountsSheet.addValidationData(accountUsageValidation); chartOfAccountsSheet.addValidationData(manualEntriesValidation); chartOfAccountsSheet.addValidationData(parentValidation); chartOfAccountsSheet.addValidationData(tagValidation); + chartOfAccountsSheet.addValidationData(officeNameValidation); + chartOfAccountsSheet.addValidationData(currencyCodeValidation); } - private void setNames(Sheet chartOfAccountsSheet, List accountTypesNoDuplicatesList) { + private void setNames(Sheet chartOfAccountsSheet, List accountTypesNoDuplicatesList, List offices) { Workbook chartOfAccountsWorkbook = chartOfAccountsSheet.getWorkbook(); for (Integer i = 0; i < accountTypesNoDuplicatesList.size(); i++) { Name tags = chartOfAccountsWorkbook.createName(); Integer[] tagValueBeginEndIndexes = accountTypeToBeginEndIndexesofAccountNames.get(i); if (accountTypeToBeginEndIndexesofAccountNames != null) { setSanitized(tags, "Tags_" + accountTypesNoDuplicatesList.get(i)); - tags.setRefersToFormula(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME + "!$S$" + tagValueBeginEndIndexes[0] - + ":$S$" + tagValueBeginEndIndexes[1]); + tags.setRefersToFormula(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME + "!$V$" + tagValueBeginEndIndexes[0] + + ":$V$" + tagValueBeginEndIndexes[1]); } Name accountNames = chartOfAccountsWorkbook.createName(); Integer[] accountNamesBeginEndIndexes = accountTypeToBeginEndIndexesofAccountNames.get(i); if (accountNamesBeginEndIndexes != null) { setSanitized(accountNames, "AccountName_" + accountTypesNoDuplicatesList.get(i)); - accountNames.setRefersToFormula(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME + "!$Q$" - + accountNamesBeginEndIndexes[0] + ":$Q$" + accountNamesBeginEndIndexes[1]); + accountNames.setRefersToFormula(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME + "!$T$" + + accountNamesBeginEndIndexes[0] + ":$T$" + accountNamesBeginEndIndexes[1]); } } + Name officeGroup = chartOfAccountsWorkbook.createName(); + officeGroup.setNameName("Office"); + officeGroup.setRefersToFormula(TemplatePopulateImportConstants.CHART_OF_ACCOUNTS_SHEET_NAME + "!$X$2:$X$" + (offices.size() + 1)); + } private void setDefaults(Sheet worksheet) { @@ -150,11 +184,15 @@ private void setDefaults(Sheet worksheet) { row = worksheet.createRow(rowNo); } writeFormula(ChartOfAcountsConstants.PARENT_ID_COL, row, - "IF(ISERROR(VLOOKUP($E" + (rowNo + 1) + ",$Q$2:$R$" + (glAccounts.size() + 1) + ",2,FALSE))," + "\"\",(VLOOKUP($E" - + (rowNo + 1) + ",$Q$2:$R$" + (glAccounts.size() + 1) + ",2,FALSE)))"); + "IF(ISERROR(VLOOKUP($E" + (rowNo + 1) + ",$T$2:$U$" + (glAccounts.size() + 1) + ",2,FALSE))," + "\"\",(VLOOKUP($E" + + (rowNo + 1) + ",$T$2:$U$" + (glAccounts.size() + 1) + ",2,FALSE)))"); writeFormula(ChartOfAcountsConstants.TAG_ID_COL, row, - "IF(ISERROR(VLOOKUP($H" + (rowNo + 1) + ",$S$2:$T$" + (glAccounts.size() + 1) + ",2,FALSE))," + "\"\",(VLOOKUP($H" - + (rowNo + 1) + ",$S$2:$T$" + (glAccounts.size() + 1) + ",2,FALSE)))"); + "IF(ISERROR(VLOOKUP($H" + (rowNo + 1) + ",$V$2:$W$" + (glAccounts.size() + 1) + ",2,FALSE))," + "\"\",(VLOOKUP($H" + + (rowNo + 1) + ",$V$2:$W$" + (glAccounts.size() + 1) + ",2,FALSE)))"); + // auto populate office id for bulk import of opening balance + writeFormula(ChartOfAcountsConstants.OFFICE_COL_ID, row, + "IF(ISERROR(VLOOKUP($K" + (rowNo + 1) + ",$X$2:$Y$" + (offices.size() + 1) + ",2,FALSE)),\"\",(VLOOKUP($K" + + (rowNo + 1) + ",$X$2:$Y$" + (offices.size() + 1) + ",2,FALSE)))"); } } catch (Exception e) { LOG.error("Problem occurred in setDefaults function", e); @@ -201,9 +239,29 @@ private void setLookupTable(Sheet chartOfAccountsSheet) { accountTypeIndex++; } } + // opening balance lookup table of offices + startIndex = 1; + rowIndex = 1; + for (OfficeData office : offices) { + startIndex = rowIndex + 1; + if (chartOfAccountsSheet.getRow(rowIndex) != null) { + Row row = chartOfAccountsSheet.getRow(rowIndex); + writeString(ChartOfAcountsConstants.LOOKUP_OFFICE_COL, row, office.name()); + writeLong(ChartOfAcountsConstants.LOOKUP_OFFICE_ID_COL, row, office.getId()); + rowIndex++; + + } else { + Row row = chartOfAccountsSheet.createRow(rowIndex); + writeString(ChartOfAcountsConstants.LOOKUP_OFFICE_COL, row, office.name()); + writeLong(ChartOfAcountsConstants.LOOKUP_OFFICE_ID_COL, row, office.getId()); + rowIndex++; + } + + } } private void setLayout(Sheet chartOfAccountsSheet) { + Row rowHeader = chartOfAccountsSheet.createRow(TemplatePopulateImportConstants.ROWHEADER_INDEX); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.ACCOUNT_TYPE_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.ACCOUNT_NAME_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); @@ -216,6 +274,11 @@ private void setLayout(Sheet chartOfAccountsSheet) { chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.TAG_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.TAG_ID_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.DESCRIPTION_COL, TemplatePopulateImportConstants.EXTRALARGE_COL_SIZE); + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.OFFICE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.OFFICE_COL_ID, TemplatePopulateImportConstants.SMALL_COL_SIZE); + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.CURRENCY_CODE, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.DEBIT_AMOUNT, TemplatePopulateImportConstants.SMALL_COL_SIZE); + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.CREDIT_AMOUNT, TemplatePopulateImportConstants.SMALL_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_ACCOUNT_TYPE_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_ACCOUNT_NAME_COL, @@ -223,6 +286,9 @@ private void setLayout(Sheet chartOfAccountsSheet) { chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_ACCOUNT_ID_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_TAG_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_TAG_ID_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); + // adding lookup for opening balance bulk import + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_OFFICE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + chartOfAccountsSheet.setColumnWidth(ChartOfAcountsConstants.LOOKUP_OFFICE_ID_COL, TemplatePopulateImportConstants.SMALL_COL_SIZE); writeString(ChartOfAcountsConstants.ACCOUNT_TYPE_COL, rowHeader, "Account Type*"); writeString(ChartOfAcountsConstants.GL_CODE_COL, rowHeader, "GL Code *"); @@ -231,14 +297,25 @@ private void setLayout(Sheet chartOfAccountsSheet) { writeString(ChartOfAcountsConstants.PARENT_COL, rowHeader, "Parent"); writeString(ChartOfAcountsConstants.PARENT_ID_COL, rowHeader, "Parent Id"); writeString(ChartOfAcountsConstants.ACCOUNT_NAME_COL, rowHeader, "Account Name"); - writeString(ChartOfAcountsConstants.TAG_COL, rowHeader, "Tag *"); + writeString(ChartOfAcountsConstants.TAG_COL, rowHeader, "Tag"); writeString(ChartOfAcountsConstants.TAG_ID_COL, rowHeader, "Tag Id"); writeString(ChartOfAcountsConstants.DESCRIPTION_COL, rowHeader, "Description *"); + // adding data for opening balance bulk import + writeString(ChartOfAcountsConstants.OFFICE_COL, rowHeader, "Parent Office for Opening Balance"); + writeString(ChartOfAcountsConstants.OFFICE_COL_ID, rowHeader, "Parent Office Code Opening Balance"); + writeString(ChartOfAcountsConstants.CURRENCY_CODE, rowHeader, "Currency Code"); + writeString(ChartOfAcountsConstants.DEBIT_AMOUNT, rowHeader, "Debit Amount"); + writeString(ChartOfAcountsConstants.CREDIT_AMOUNT, rowHeader, "Credit Amount"); + writeString(ChartOfAcountsConstants.LOOKUP_ACCOUNT_TYPE_COL, rowHeader, "Lookup Account type"); writeString(ChartOfAcountsConstants.LOOKUP_TAG_COL, rowHeader, "Lookup Tag"); writeString(ChartOfAcountsConstants.LOOKUP_TAG_ID_COL, rowHeader, "Lookup Tag Id"); writeString(ChartOfAcountsConstants.LOOKUP_ACCOUNT_NAME_COL, rowHeader, "Lookup Account name *"); writeString(ChartOfAcountsConstants.LOOKUP_ACCOUNT_ID_COL, rowHeader, "Lookup Account Id"); + // adding lookup for opening balance bulk import + writeString(ChartOfAcountsConstants.LOOKUP_OFFICE_COL, rowHeader, "Lookup Office Name"); + writeString(ChartOfAcountsConstants.LOOKUP_OFFICE_ID_COL, rowHeader, "Lookup Office Id"); } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/loan/LoanWorkbookPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/loan/LoanWorkbookPopulator.java index b78c1334a87..a1257504d5e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/loan/LoanWorkbookPopulator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/loan/LoanWorkbookPopulator.java @@ -22,12 +22,14 @@ import org.apache.fineract.infrastructure.bulkimport.constants.LoanConstants; import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; import org.apache.fineract.infrastructure.bulkimport.populator.AbstractWorkbookPopulator; +import org.apache.fineract.infrastructure.bulkimport.populator.ChargeSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.ClientSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.ExtrasSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.GroupSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.LoanProductSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.OfficeSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.PersonnelSheetPopulator; +import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.poi.hssf.usermodel.HSSFDataValidationHelper; import org.apache.poi.hssf.usermodel.HSSFSheet; @@ -46,22 +48,25 @@ public class LoanWorkbookPopulator extends AbstractWorkbookPopulator { - private final OfficeSheetPopulator officeSheetPopulator; - private final ClientSheetPopulator clientSheetPopulator; - private final GroupSheetPopulator groupSheetPopulator; - private final PersonnelSheetPopulator personnelSheetPopulator; - private final LoanProductSheetPopulator productSheetPopulator; - private final ExtrasSheetPopulator extrasSheetPopulator; + private OfficeSheetPopulator officeSheetPopulator; + private ClientSheetPopulator clientSheetPopulator; + private GroupSheetPopulator groupSheetPopulator; + private PersonnelSheetPopulator personnelSheetPopulator; + private LoanProductSheetPopulator productSheetPopulator; + private ChargeSheetPopulator chargeSheetPopulator; + private ExtrasSheetPopulator extrasSheetPopulator; public LoanWorkbookPopulator(OfficeSheetPopulator officeSheetPopulator, ClientSheetPopulator clientSheetPopulator, GroupSheetPopulator groupSheetPopulator, PersonnelSheetPopulator personnelSheetPopulator, - LoanProductSheetPopulator productSheetPopulator, ExtrasSheetPopulator extrasSheetPopulator) { + LoanProductSheetPopulator productSheetPopulator, ChargeSheetPopulator chargeSheetPopulator, + ExtrasSheetPopulator extrasSheetPopulator) { this.officeSheetPopulator = officeSheetPopulator; this.clientSheetPopulator = clientSheetPopulator; this.groupSheetPopulator = groupSheetPopulator; this.personnelSheetPopulator = personnelSheetPopulator; this.productSheetPopulator = productSheetPopulator; this.extrasSheetPopulator = extrasSheetPopulator; + this.chargeSheetPopulator = chargeSheetPopulator; } @Override @@ -72,6 +77,7 @@ public void populate(Workbook workbook, String dateFormat) { groupSheetPopulator.populate(workbook, dateFormat); personnelSheetPopulator.populate(workbook, dateFormat); productSheetPopulator.populate(workbook, dateFormat); + chargeSheetPopulator.populate(workbook, dateFormat); extrasSheetPopulator.populate(workbook, dateFormat); setLayout(loanSheet); setRules(loanSheet, dateFormat); @@ -138,6 +144,16 @@ private void setRules(Sheet worksheet, String dateFormat) { LoanConstants.LAST_REPAYMENT_DATE_COL, LoanConstants.LAST_REPAYMENT_DATE_COL); DataValidationHelper validationHelper = new HSSFDataValidationHelper((HSSFSheet) worksheet); + CellRangeAddressList chargeOneNameRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + LoanConstants.CHARGE_NAME_1, LoanConstants.CHARGE_NAME_1); + CellRangeAddressList chargeOneAmountTypeRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + LoanConstants.CHARGE_AMOUNT_TYPE_1, LoanConstants.CHARGE_AMOUNT_TYPE_1); + + CellRangeAddressList chargeTwoNameRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + LoanConstants.CHARGE_NAME_2, LoanConstants.CHARGE_NAME_2); + CellRangeAddressList chargeTwoAmountTypeRange = new CellRangeAddressList(1, SpreadsheetVersion.EXCEL97.getLastRowIndex(), + LoanConstants.CHARGE_AMOUNT_TYPE_2, LoanConstants.CHARGE_AMOUNT_TYPE_2); + setNames(worksheet); DataValidationConstraint officeNameConstraint = validationHelper.createFormulaListConstraint("Office"); @@ -168,9 +184,11 @@ private void setRules(Sheet worksheet, String dateFormat) { DataValidationConstraint.OperatorType.BETWEEN, "=INDIRECT(CONCATENATE(\"MIN_REPAYMENT_\",$E1))", "=INDIRECT(CONCATENATE(\"MAX_REPAYMENT_\",$E1))"); DataValidationConstraint frequencyConstraint = validationHelper + .createExplicitListConstraint(new String[] { "Days", "Weeks", "Months", "Semi Month" }); + DataValidationConstraint loanTermFrequencyConstraint = validationHelper .createExplicitListConstraint(new String[] { "Days", "Weeks", "Months" }); DataValidationConstraint loanTermConstraint = validationHelper - .createIntegerConstraint(DataValidationConstraint.OperatorType.GREATER_OR_EQUAL, "=$M1*$N1", null); + .createIntegerConstraint(DataValidationConstraint.OperatorType.GREATER_OR_EQUAL, "=$M1/$N1", "=$M1*$N1"); DataValidationConstraint interestFrequencyConstraint = validationHelper .createFormulaListConstraint("INDIRECT(CONCATENATE(\"INTEREST_FREQUENCY_\",$E1))"); DataValidationConstraint interestConstraint = validationHelper.createDecimalConstraint( @@ -196,6 +214,14 @@ private void setRules(Sheet worksheet, String dateFormat) { DataValidationConstraint lastRepaymentDateConstraint = validationHelper .createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN, "=$I1", "=TODAY()", dateFormat); + DataValidationConstraint chargeOneNameConstraint = validationHelper.createFormulaListConstraint("Charges"); + DataValidationConstraint chargeOneAmountTypeConstraint = validationHelper + .createExplicitListConstraint(new String[] { "Flat", "% Amount" }); + + DataValidationConstraint chargeTwoNameConstraint = validationHelper.createFormulaListConstraint("Charges"); + DataValidationConstraint chargeTwoAmountTypeConstraint = validationHelper + .createExplicitListConstraint(new String[] { "Flat", "% Amount" }); + DataValidation officeValidation = validationHelper.createValidation(officeNameConstraint, officeNameRange); DataValidation loanTypeValidation = validationHelper.createValidation(loanTypeConstraint, loanTypeRange); DataValidation clientValidation = validationHelper.createValidation(clientNameConstraint, clientNameRange); @@ -203,7 +229,7 @@ private void setRules(Sheet worksheet, String dateFormat) { DataValidation loanOfficerValidation = validationHelper.createValidation(loanOfficerNameConstraint, loanOfficerRange); DataValidation fundNameValidation = validationHelper.createValidation(fundNameConstraint, fundNameRange); DataValidation repaidFrequencyValidation = validationHelper.createValidation(frequencyConstraint, repaidFrequencyRange); - DataValidation loanTermFrequencyValidation = validationHelper.createValidation(frequencyConstraint, loanTermFrequencyRange); + DataValidation loanTermFrequencyValidation = validationHelper.createValidation(loanTermFrequencyConstraint, loanTermFrequencyRange); DataValidation amortizationValidation = validationHelper.createValidation(amortizationConstraint, amortizationRange); DataValidation interestMethodValidation = validationHelper.createValidation(interestMethodConstraint, interestMethodRange); DataValidation interestCalculationPeriodValidation = validationHelper.createValidation(interestCalculationPeriodConstraint, @@ -228,6 +254,14 @@ private void setRules(Sheet worksheet, String dateFormat) { graceOnInterestChargedRange); DataValidation interestFrequencyValidation = validationHelper.createValidation(interestFrequencyConstraint, interestFrequencyRange); + DataValidation chargeOneNameValidation = validationHelper.createValidation(chargeOneNameConstraint, chargeOneNameRange); + DataValidation chargeOneAmountTypeValidation = validationHelper.createValidation(chargeOneAmountTypeConstraint, + chargeOneAmountTypeRange); + + DataValidation chargeTwoNameValidation = validationHelper.createValidation(chargeTwoNameConstraint, chargeTwoNameRange); + DataValidation chargeTwoAmountTypeValidation = validationHelper.createValidation(chargeTwoAmountTypeConstraint, + chargeTwoAmountTypeRange); + interestFrequencyValidation.setSuppressDropDownArrow(true); worksheet.addValidationData(officeValidation); @@ -258,6 +292,14 @@ private void setRules(Sheet worksheet, String dateFormat) { worksheet.addValidationData(lastRepaymentDateValidation); worksheet.addValidationData(repaymentTypeValidation); + worksheet.addValidationData(chargeOneNameValidation); + // worksheet.addValidationData(chargeOneAmountValidation); + worksheet.addValidationData(chargeOneAmountTypeValidation); + + worksheet.addValidationData(chargeTwoNameValidation); + // worksheet.addValidationData(chargeTwoAmountValidation); + worksheet.addValidationData(chargeTwoAmountTypeValidation); + } private void setLayout(Sheet worksheet) { @@ -299,11 +341,13 @@ private void setLayout(Sheet worksheet) { worksheet.setColumnWidth(LoanConstants.LOOKUP_CLIENT_EXTERNAL_ID, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.LOOKUP_ACTIVATION_DATE_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.EXTERNAL_ID_COL, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); - worksheet.setColumnWidth(LoanConstants.CHARGE_ID_1, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(LoanConstants.CHARGE_NAME_1, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.CHARGE_AMOUNT_1, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(LoanConstants.CHARGE_AMOUNT_TYPE_1, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.CHARGE_DUE_DATE_1, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); - worksheet.setColumnWidth(LoanConstants.CHARGE_ID_2, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(LoanConstants.CHARGE_NAME_2, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.CHARGE_AMOUNT_2, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); + worksheet.setColumnWidth(LoanConstants.CHARGE_AMOUNT_TYPE_2, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.CHARGE_DUE_DATE_2, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.GROUP_ID, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); worksheet.setColumnWidth(LoanConstants.LINK_ACCOUNT_ID, TemplatePopulateImportConstants.MEDIUM_COL_SIZE); @@ -341,11 +385,13 @@ private void setLayout(Sheet worksheet) { writeString(LoanConstants.LOOKUP_CLIENT_EXTERNAL_ID, rowHeader, "Lookup Client ExternalID"); writeString(LoanConstants.LOOKUP_ACTIVATION_DATE_COL, rowHeader, "Client Activation Date"); writeString(LoanConstants.EXTERNAL_ID_COL, rowHeader, "External Id"); - writeString(LoanConstants.CHARGE_ID_1, rowHeader, "Charge Id"); + writeString(LoanConstants.CHARGE_NAME_1, rowHeader, "Charge Name*"); writeString(LoanConstants.CHARGE_AMOUNT_1, rowHeader, "Charged Amount"); + writeString(LoanConstants.CHARGE_AMOUNT_TYPE_1, rowHeader, "Charged Amount Type"); writeString(LoanConstants.CHARGE_DUE_DATE_1, rowHeader, "Charged On Date"); - writeString(LoanConstants.CHARGE_ID_2, rowHeader, "Charge Id"); + writeString(LoanConstants.CHARGE_NAME_2, rowHeader, "Charge Name*"); writeString(LoanConstants.CHARGE_AMOUNT_2, rowHeader, "Charged Amount"); + writeString(LoanConstants.CHARGE_AMOUNT_TYPE_2, rowHeader, "Charged Amount Type"); writeString(LoanConstants.CHARGE_DUE_DATE_2, rowHeader, "Charged On Date"); writeString(LoanConstants.GROUP_ID, rowHeader, "GROUP ID"); writeString(LoanConstants.LINK_ACCOUNT_ID, rowHeader, "Linked Account No."); @@ -385,8 +431,11 @@ private void setDefaults(Sheet worksheet) { writeFormula(LoanConstants.NO_OF_REPAYMENTS_COL, row, "IF(ISERROR(INDIRECT(CONCATENATE(\"NO_REPAYMENT_\",$E" + (rowNo + 1) + "))),\"\",INDIRECT(CONCATENATE(\"NO_REPAYMENT_\",$E" + (rowNo + 1) + ")))"); writeFormula(LoanConstants.LOAN_TERM_COL, row, - "IF(ISERROR($M" + (rowNo + 1) + "*$N" + (rowNo + 1) + "),\"\",$M" + (rowNo + 1) + "*$N" + (rowNo + 1) + ")"); - writeFormula(LoanConstants.LOAN_TERM_FREQUENCY_COL, row, "$O" + (rowNo + 1)); + "IF(($O" + (rowNo + 1) + "=\"Semi Month\"), " + "(IF(ISERROR($M" + (rowNo + 1) + "/$N" + (rowNo + 1) + "),\"\",$M" + + (rowNo + 1) + "/$N" + (rowNo + 1) + ")), " + "(IF(ISERROR($M" + (rowNo + 1) + "*$N" + (rowNo + 1) + + "),\"\",$M" + (rowNo + 1) + "*$N" + (rowNo + 1) + "))" + ")"); + writeFormula(LoanConstants.LOAN_TERM_FREQUENCY_COL, row, + "IF(($O" + (rowNo + 1) + "=\"Semi Month\"), \"Months\", $O" + (rowNo + 1) + ")"); writeFormula(LoanConstants.NOMINAL_INTEREST_RATE_FREQUENCY_COL, row, "IF(ISERROR(INDIRECT(CONCATENATE(\"INTEREST_FREQUENCY_\",$E" + (rowNo + 1) + "))),\"\",INDIRECT(CONCATENATE(\"INTEREST_FREQUENCY_\",$E" + (rowNo + 1) + ")))"); @@ -408,13 +457,13 @@ private void setDefaults(Sheet worksheet) { + (rowNo + 1) + "))),\"\",INDIRECT(CONCATENATE(\"GRACE_INTEREST_PAYMENT_\",$E" + (rowNo + 1) + ")))"); writeFormula(LoanConstants.GRACE_ON_INTEREST_CHARGED_COL, row, "IF(ISERROR(INDIRECT(CONCATENATE(\"GRACE_INTEREST_CHARGED_\",$E" + (rowNo + 1) + "))),\"\",INDIRECT(CONCATENATE(\"GRACE_INTEREST_CHARGED_\",$E" + (rowNo + 1) + ")))"); - } } private void setNames(Sheet worksheet) { Workbook loanWorkbook = worksheet.getWorkbook(); List officeNames = officeSheetPopulator.getOfficeNames(); + List charges = chargeSheetPopulator.getCharges(); List products = productSheetPopulator.getProducts(); // Office Names @@ -460,6 +509,31 @@ private void setNames(Sheet worksheet) { fundGroup.setRefersToFormula( TemplatePopulateImportConstants.EXTRAS_SHEET_NAME + "!$B$2:$B$" + (extrasSheetPopulator.getFundsSize() + 1)); + // Charge Name + Name chargeGroup = loanWorkbook.createName(); + chargeGroup.setNameName("Charges"); + chargeGroup.setRefersToFormula( + TemplatePopulateImportConstants.CHARGE_SHEET_NAME + "!$B$2:$B$" + (chargeSheetPopulator.getChargesSize() + 1)); + + // Default Charge Name, Charge Amount, Charge Amount Type, Charge Due Date + for (Integer i = 0; i < charges.size(); i++) { + Name chargeColName = loanWorkbook.createName(); + Name chargeAmount = loanWorkbook.createName(); + Name chargeAmountType = loanWorkbook.createName(); + + String chargeName = charges.get(i).getName().trim().replaceAll("[ )(]", "_"); + + chargeColName.setNameName("CHARGE_NAME_" + chargeName); + chargeColName.setRefersToFormula(TemplatePopulateImportConstants.CHARGE_SHEET_NAME + "!$B$" + (i + 2)); + + chargeAmount.setNameName("CHARGE_AMOUNT_" + chargeName); + chargeAmount.setRefersToFormula(TemplatePopulateImportConstants.CHARGE_SHEET_NAME + "!$C$" + (i + 2)); + + chargeAmountType.setNameName("CHARGE_AMOUNT_TYPE_" + chargeName); + chargeAmountType.setRefersToFormula(TemplatePopulateImportConstants.CHARGE_SHEET_NAME + "!$D$" + (i + 2)); + + } + // Payment Type Name Name paymentTypeGroup = loanWorkbook.createName(); paymentTypeGroup.setNameName("PaymentTypes"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java index 34b451c028f..0bff74555a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java @@ -29,6 +29,7 @@ import org.apache.fineract.infrastructure.bulkimport.constants.TemplatePopulateImportConstants; import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType; import org.apache.fineract.infrastructure.bulkimport.populator.CenterSheetPopulator; +import org.apache.fineract.infrastructure.bulkimport.populator.ChargeSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.ClientSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.ExtrasSheetPopulator; import org.apache.fineract.infrastructure.bulkimport.populator.FixedDepositProductSheetPopulator; @@ -195,7 +196,7 @@ public Response getTemplate(String entityType, Long officeId, Long staffId, fina } else if (entityType.trim().equalsIgnoreCase(GlobalEntityType.OFFICES.toString())) { populator = populateOfficeWorkbook(); } else if (entityType.trim().equalsIgnoreCase(GlobalEntityType.CHART_OF_ACCOUNTS.toString())) { - populator = populateChartOfAccountsWorkbook(); + populator = populateChartOfAccountsWorkbook(officeId); } else if (entityType.trim().equalsIgnoreCase(GlobalEntityType.STAFF.toString())) { populator = populateStaffWorkbook(officeId); } else if (entityType.trim().equalsIgnoreCase(GlobalEntityType.SHARE_ACCOUNTS.toString())) { @@ -277,6 +278,11 @@ private List fetchOffices(final Long officeId) { return offices; } + @SuppressWarnings("unchecked") + private List fetchCharges() { + return (List) this.chargeReadPlatformService.retrieveAllCharges(); + } + @SuppressWarnings("unchecked") private List fetchStaff(final Long staffId) { List staff = null; @@ -376,13 +382,15 @@ private WorkbookPopulator populateLoanWorkbook(Long officeId, Long staffId) { List staff = fetchStaff(staffId); List clients = fetchClients(officeId); List groups = fetchGroups(officeId); + List charges = fetchCharges(); List loanproducts = fetchLoanProducts(); List funds = fetchFunds(); List paymentTypes = fetchPaymentTypes(); List currencies = fetchCurrencies(); return new LoanWorkbookPopulator(new OfficeSheetPopulator(offices), new ClientSheetPopulator(clients, offices), new GroupSheetPopulator(groups, offices), new PersonnelSheetPopulator(staff, offices), - new LoanProductSheetPopulator(loanproducts), new ExtrasSheetPopulator(funds, paymentTypes, currencies)); + new LoanProductSheetPopulator(loanproducts), new ChargeSheetPopulator(charges), + new ExtrasSheetPopulator(funds, paymentTypes, currencies)); } private List fetchCurrencies() { @@ -495,10 +503,12 @@ private WorkbookPopulator populateOfficeWorkbook() { return new OfficeWorkbookPopulator(offices); } - private WorkbookPopulator populateChartOfAccountsWorkbook() { + private WorkbookPopulator populateChartOfAccountsWorkbook(Long officeId) { this.context.authenticatedUser().validateHasReadPermission(TemplatePopulateImportConstants.GL_ACCOUNT_ENTITY_TYPE); List glAccounts = fetchGLAccounts(); - return new ChartOfAccountsWorkbook(glAccounts); + List offices = fetchOffices(null); + return new ChartOfAccountsWorkbook(glAccounts, offices, + (List) this.currencyReadPlatformService.retrieveAllowedCurrencies()); } private WorkbookPopulator populateStaffWorkbook(Long officeId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookService.java index a603eff4136..a8ecd9d6c32 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookService.java @@ -18,13 +18,13 @@ */ package org.apache.fineract.infrastructure.bulkimport.service; -import com.sun.jersey.core.header.FormDataContentDisposition; import java.io.InputStream; import java.util.Collection; import javax.ws.rs.core.Response; import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType; import org.apache.fineract.infrastructure.bulkimport.data.ImportData; import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; public interface BulkImportWorkbookService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java index 42552947826..da9cc962678 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.infrastructure.bulkimport.service; -import com.sun.jersey.core.header.FormDataContentDisposition; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -30,6 +29,7 @@ import java.time.LocalDate; import java.util.Collection; import javax.ws.rs.core.Response; +import org.apache.commons.io.IOUtils; import org.apache.fineract.infrastructure.bulkimport.data.BulkImportEvent; import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType; import org.apache.fineract.infrastructure.bulkimport.data.ImportData; @@ -50,8 +50,8 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Workbook; import org.apache.tika.Tika; -import org.apache.tika.io.IOUtils; import org.apache.tika.io.TikaInputStream; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java index 0712cff1f15..742a7d3e737 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java @@ -22,18 +22,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurer; +import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.interceptor.CacheErrorHandler; -import org.springframework.cache.interceptor.CacheResolver; -import org.springframework.cache.interceptor.KeyGenerator; -import org.springframework.cache.interceptor.SimpleKeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -@SuppressWarnings("deprecation") @Configuration @EnableCaching -public class PlatformCacheConfiguration implements CachingConfigurer { +public class PlatformCacheConfiguration extends CachingConfigurerSupport implements CachingConfigurer { @Autowired private RuntimeDelegatingCacheManager delegatingCacheManager; @@ -43,21 +39,4 @@ public class PlatformCacheConfiguration implements CachingConfigurer { public CacheManager cacheManager() { return this.delegatingCacheManager; } - - @Override - public CacheResolver cacheResolver() { - // TODO https://issues.apache.org/jira/browse/FINERACT-705 - return null; - } - - @Override - public KeyGenerator keyGenerator() { - return new SimpleKeyGenerator(); - } - - @Override - public CacheErrorHandler errorHandler() { - // TODO https://issues.apache.org/jira/browse/FINERACT-705 - return null; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java index af9e412d15a..74225c10389 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java @@ -47,13 +47,13 @@ public class RuntimeDelegatingCacheManager implements CacheManager { private static final Logger LOG = LoggerFactory.getLogger(RuntimeDelegatingCacheManager.class); - private final JCacheCacheManager jcacheCacheManager; + private final CacheManager cacheManager; private final CacheManager noOpCacheManager = new NoOpCacheManager(); private CacheManager currentCacheManager; @Autowired - public RuntimeDelegatingCacheManager(final JCacheCacheManager jcacheCacheManager) { - this.jcacheCacheManager = jcacheCacheManager; + public RuntimeDelegatingCacheManager(final JCacheCacheManager cacheManager) { + this.cacheManager = cacheManager; this.currentCacheManager = this.noOpCacheManager; } @@ -109,7 +109,7 @@ public Map switchToCache(final boolean ehcacheEnabled, final Cac changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue()); clearEhCache(); } - this.currentCacheManager = this.jcacheCacheManager; + this.currentCacheManager = this.cacheManager; if (this.currentCacheManager.getCacheNames().size() == 0) { LOG.error("No caches configured for activated CacheManager {}", this.currentCacheManager); @@ -126,7 +126,6 @@ public Map switchToCache(final boolean ehcacheEnabled, final Cac } private void clearEhCache() { - javax.cache.CacheManager cacheManager = this.jcacheCacheManager.getCacheManager(); Iterable cacheNames = cacheManager.getCacheNames(); for (String cacheName : cacheNames) { cacheManager.getCache(cacheName).clear(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailDataValidator.java index 7b8a31661de..04cfa5603a3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailDataValidator.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.infrastructure.campaigns.email.data; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Splitter; import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; @@ -41,8 +43,6 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java index b57d81bed39..72ac2264bd6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java @@ -20,6 +20,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -74,11 +75,11 @@ public class EmailCampaign extends AbstractPersistableCustom { @Column(name = "email_message", nullable = false) private String emailMessage; - @Column(name = "email_attachment_file_format", nullable = false) + @Column(name = "email_attachment_file_format") private String emailAttachmentFileFormat; @ManyToOne - @JoinColumn(name = "stretchy_report_id", nullable = false) + @JoinColumn(name = "stretchy_report_id") private Report stretchyReport; @Column(name = "stretchy_report_param_map", nullable = true) @@ -108,18 +109,18 @@ public class EmailCampaign extends AbstractPersistableCustom { @JoinColumn(name = "approvedon_userid", nullable = true) private AppUser approvedBy; - @Column(name = "recurrence", nullable = false) + @Column(name = "recurrence") private String recurrence; - @Column(name = "next_trigger_date", nullable = false) + @Column(name = "next_trigger_date") @Temporal(TemporalType.TIMESTAMP) private Date nextTriggerDate; - @Column(name = "last_trigger_date", nullable = false) + @Column(name = "last_trigger_date") @Temporal(TemporalType.TIMESTAMP) private Date lastTriggerDate; - @Column(name = "recurrence_start_date", nullable = false) + @Column(name = "recurrence_start_date") @Temporal(TemporalType.TIMESTAMP) private Date recurrenceStartDate; @@ -152,7 +153,7 @@ private EmailCampaign(final String campaignName, final Integer campaignType, fin this.emailAttachmentFileFormat = emailAttachmentFileFormat.getValue(); this.stretchyReport = stretchyReport; this.stretchyReportParamMap = stretchyReportParamMap; - this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.submittedBy = submittedBy; this.recurrence = recurrence; LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); @@ -274,7 +275,7 @@ public void activate(final AppUser currentUser, final DateTimeFormatter formatte throw new PlatformApiDataValidationException(dataValidationErrors); } - this.approvedOnDate = Date.from(activationLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.approvedOnDate = Date.from(activationLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.approvedBy = currentUser; this.status = EmailCampaignStatus.ACTIVE.getValue(); @@ -298,7 +299,7 @@ public void close(final AppUser currentUser, final DateTimeFormatter dateTimeFor this.lastTriggerDate = null; } this.closedBy = currentUser; - this.closureDate = Date.from(closureLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.closureDate = Date.from(closureLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.status = EmailCampaignStatus.CLOSED.getValue(); validateClosureDate(); } @@ -317,7 +318,7 @@ public void reactivate(final AppUser currentUser, final DateTimeFormatter dateTi throw new PlatformApiDataValidationException(dataValidationErrors); } - this.approvedOnDate = Date.from(reactivateLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.approvedOnDate = Date.from(reactivateLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.status = EmailCampaignStatus.ACTIVE.getValue(); this.approvedBy = currentUser; this.closureDate = null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java index 291698b912b..8a0eec2945f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java @@ -18,7 +18,14 @@ */ package org.apache.fineract.infrastructure.campaigns.email.domain; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; -public interface EmailCampaignRepository extends JpaRepository, JpaSpecificationExecutor {} +public interface EmailCampaignRepository extends JpaRepository, JpaSpecificationExecutor { + + @Query("SELECT campaign FROM EmailCampaign campaign WHERE campaign.paramValue LIKE :reportPattern AND campaign.campaignType=:type AND campaign.status=300") + List findActiveEmailCampaigns(@Param("reportPattern") String reportPattern, @Param("type") Integer type); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java index 95c36ce00ba..79059eaadd6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java @@ -20,7 +20,8 @@ public enum EmailCampaignType { - DIRECT(1, "emailCampaignStatusType.direct"), SCHEDULE(2, "emailCampaignStatusType.schedule"); + DIRECT(1, "emailCampaignStatusType.direct"), SCHEDULE(2, "emailCampaignStatusType.schedule"), TRIGGERED(3, + "emailCampaignStatusType.triggered"); private final Integer value; private final String code; @@ -47,6 +48,9 @@ public static EmailCampaignType fromInt(final Integer typeValue) { case 2: type = SCHEDULE; break; + case 3: + type = TRIGGERED; + break; } return type; } @@ -58,4 +62,8 @@ public boolean isDirect() { public boolean isSchedule() { return this.value.equals(EmailCampaignType.SCHEDULE.getValue()); } + + public boolean isTriggered() { + return this.value.equals(EmailCampaignType.TRIGGERED.getValue()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailMessage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailMessage.java index d71dc5dd73d..da065211497 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailMessage.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailMessage.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.campaigns.email.domain; import java.time.LocalDate; +import java.time.ZoneId; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; @@ -109,7 +110,7 @@ private EmailMessage(final Group group, final Client client, final Staff staff, this.message = message; this.campaignName = campaignName; this.submittedOnDate = Date - .from(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + .from(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).atStartOfDay(ZoneId.systemDefault()).toInstant()); } public Map update(final JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java new file mode 100644 index 00000000000..c02d697a9bc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.email.service; + +public interface EmailCampaignDomainService {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java new file mode 100644 index 00000000000..b6a0f2e5662 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java @@ -0,0 +1,151 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.email.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.PostConstruct; +import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign; +import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants; +import org.apache.fineract.portfolio.common.service.BusinessEventListener; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmailCampaignDomainServiceImpl implements EmailCampaignDomainService { + + private static final Logger LOG = LoggerFactory.getLogger(EmailCampaignDomainServiceImpl.class); + private final BusinessEventNotifierService businessEventNotifierService; + private final EmailCampaignWritePlatformService emailCampaignWritePlatformService; + private final EmailCampaignRepository emailCampaignRepository; + + @Autowired + public EmailCampaignDomainServiceImpl(BusinessEventNotifierService businessEventNotifierService, + EmailCampaignWritePlatformService emailCampaignWritePlatformService, EmailCampaignRepository emailCampaignRepository) { + this.businessEventNotifierService = businessEventNotifierService; + this.emailCampaignWritePlatformService = emailCampaignWritePlatformService; + this.emailCampaignRepository = emailCampaignRepository; + } + + @PostConstruct + public void addListeners() { + this.businessEventNotifierService.addBusinessEventPostListeners(BusinessEventNotificationConstants.BusinessEvents.LOAN_APPROVED, + new EmailCampaignDomainServiceImpl.SendEmailOnLoanApproved()); + this.businessEventNotifierService.addBusinessEventPostListeners(BusinessEventNotificationConstants.BusinessEvents.LOAN_REJECTED, + new EmailCampaignDomainServiceImpl.SendEmailOnLoanRejected()); + this.businessEventNotifierService.addBusinessEventPostListeners( + BusinessEventNotificationConstants.BusinessEvents.LOAN_MAKE_REPAYMENT, + new EmailCampaignDomainServiceImpl.SendEmailOnLoanRepayment()); + } + + private class SendEmailOnLoanRepayment extends EmailBusinessEventAdapter { + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BusinessEntity.LOAN_TRANSACTION); + if (entity instanceof LoanTransaction) { + LoanTransaction loanTransaction = (LoanTransaction) entity; + try { + notifyLoanOwner(loanTransaction, "Loan Repayment"); + } catch (IOException e) { + LOG.error("Exception when trying to send triggered email: {}", e.getMessage()); + } + } + } + } + + private class SendEmailOnLoanRejected extends EmailBusinessEventAdapter { + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BusinessEntity.LOAN); + if (entity instanceof Loan) { + Loan loan = (Loan) entity; + try { + notifyLoanOwner(loan, "Loan Rejected"); + } catch (IOException e) { + LOG.error("Exception when trying to send triggered email: {}", e.getMessage()); + } + } + } + } + + private class SendEmailOnLoanApproved extends EmailBusinessEventAdapter { + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BusinessEntity.LOAN); + if (entity instanceof Loan) { + Loan loan = (Loan) entity; + try { + notifyLoanOwner(loan, "Loan Approved"); + } catch (IOException e) { + LOG.error("Exception when trying to send triggered email: {}", e.getMessage()); + } + } + } + } + + private void notifyLoanOwner(LoanTransaction loanTransaction, String paramValue) throws IOException { + List campaigns = this.retrieveEmailCampaigns(paramValue); + for (EmailCampaign emailCampaign : campaigns) { + HashMap campaignParams = new ObjectMapper().readValue(emailCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("loanId", loanTransaction.getLoan().getId().toString()); + campaignParams.put("loanTransactionId", loanTransaction.getId().toString()); + this.emailCampaignWritePlatformService.insertDirectCampaignIntoEmailOutboundTable(loanTransaction.getLoan(), emailCampaign, + campaignParams); + } + } + + private void notifyLoanOwner(Loan loan, String paramValue) throws IOException { + List campaigns = this.retrieveEmailCampaigns(paramValue); + for (EmailCampaign emailCampaign : campaigns) { + HashMap campaignParams = new ObjectMapper().readValue(emailCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("loanId", loan.getId().toString()); + this.emailCampaignWritePlatformService.insertDirectCampaignIntoEmailOutboundTable(loan, emailCampaign, campaignParams); + } + } + + private abstract static class EmailBusinessEventAdapter implements BusinessEventListener { + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + // Nothing to do + } + } + + private List retrieveEmailCampaigns(String paramValue) { + List emailCampaigns = emailCampaignRepository.findActiveEmailCampaigns("%" + paramValue + "%", + SmsCampaignTriggerType.TRIGGERED.getValue()); + return emailCampaigns; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java index 5ba082d66a1..313cf3524fa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.infrastructure.campaigns.email.service; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; @@ -89,8 +91,6 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; import org.apache.fineract.useradministration.domain.AppUser; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -158,20 +158,24 @@ public CommandProcessingResult create(JsonCommand command) { final Long reportId = command.longValueOfParameterNamed(EmailCampaignValidator.stretchyReportId); - final Report report = this.reportRepository.findById(reportId).orElseThrow(() -> new ReportNotFoundException(reportId)); + Report report = null; + Map stretchyReportParams = null; + if (reportId != null) { + report = this.reportRepository.findById(reportId).orElseThrow(() -> new ReportNotFoundException(reportId)); + final Set reportParameterUsages = report.getReportParameterUsages(); + stretchyReportParams = new HashMap<>(); - // find all report parameters and store them as json string - final Set reportParameterUsages = report.getReportParameterUsages(); - final Map stretchyReportParams = new HashMap<>(); - - if (reportParameterUsages != null && !reportParameterUsages.isEmpty()) { - for (final ReportParameterUsage reportParameterUsage : reportParameterUsages) { - stretchyReportParams.put(reportParameterUsage.getReportParameterName(), ""); + if (reportParameterUsages != null && !reportParameterUsages.isEmpty()) { + for (final ReportParameterUsage reportParameterUsage : reportParameterUsages) { + stretchyReportParams.put(reportParameterUsage.getReportParameterName(), ""); + } } } EmailCampaign emailCampaign = EmailCampaign.instance(currentUser, businessRule, report, command); - emailCampaign.setStretchyReportParamMap(new Gson().toJson(stretchyReportParams)); + if (stretchyReportParams != null) { + emailCampaign.setStretchyReportParamMap(new Gson().toJson(stretchyReportParams)); + } this.emailCampaignRepository.save(emailCampaign); @@ -241,6 +245,32 @@ public CommandProcessingResult delete(final Long resourceId) { } + @Override + public void insertDirectCampaignIntoEmailOutboundTable(final Loan loan, final EmailCampaign emailCampaign, + HashMap campaignParams) { + try { + List> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"), + campaignParams); + + if (runReportObject != null) { + for (HashMap entry : runReportObject) { + String message = this.compileEmailTemplate(emailCampaign.getEmailMessage(), emailCampaign.getCampaignName(), entry); + Client client = loan.getClient(); + String emailAddress = client.emailAddress(); + + if (emailAddress != null && isValidEmail(emailAddress)) { + EmailMessage emailMessage = EmailMessage.pendingEmail(null, client, null, emailCampaign, + emailCampaign.getEmailSubject(), message, emailAddress, emailCampaign.getCampaignName()); + this.emailMessageRepository.save(emailMessage); + } + } + } + } catch (final IOException e) { + // TODO throw something here + } + + } + private void insertDirectCampaignIntoEmailOutboundTable(final String emailParams, final String emailSubject, final String messageTemplate, final String campaignName, final Long campaignId) { try { @@ -589,8 +619,11 @@ public void sendEmailMessage() throws JobExecutionException { final EmailCampaign emailCampaign = this.emailCampaignRepository.findById(emailMessage.getEmailCampaign().getId()) .orElse(null); // - final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat - .instance(emailCampaign.getEmailAttachmentFileFormat()); + ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = null; + if (emailCampaign.getEmailAttachmentFileFormat() != null) { + emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat + .instance(emailCampaign.getEmailAttachmentFileFormat()); + } final List attachmentList = new ArrayList<>(); @@ -690,29 +723,16 @@ public void sendEmailMessage() throws JobExecutionException { final EmailMessageWithAttachmentData emailMessageWithAttachmentData = EmailMessageWithAttachmentData.createNew( emailMessage.getEmailAddress(), emailMessage.getMessage(), emailMessage.getEmailSubject(), attachmentList); - - if (!attachmentList.isEmpty() && attachmentList.size() > 0) { // only - // send - // email - // message - // if - // there - // is - // an - // attachment - // to - // it + try { this.emailMessageJobEmailService.sendEmailWithAttachment(emailMessageWithAttachmentData); emailMessage.setStatusType(EmailMessageStatusType.SENT.getValue()); this.emailMessageRepository.save(emailMessage); - } else { - emailMessage.updateErrorMessage(errorLog.toString()); - + } catch (Exception e) { + emailMessage.updateErrorMessage(e.getMessage()); emailMessage.setStatusType(EmailMessageStatusType.FAILED.getValue()); - this.emailMessageRepository.save(emailMessage); } } @@ -734,7 +754,9 @@ public void sendEmailMessage() throws JobExecutionException { */ private File generateAttachments(final EmailCampaign emailCampaign, final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat, final Map reportParams, final String reportName, final StringBuilder errorLog) { - + if (reportName == null) { + return null; + } try { final ByteArrayOutputStream byteArrayOutputStream = this.readReportingService.generatePentahoReportAsOutputStream(reportName, emailAttachmentFileFormat.getValue(), reportParams, null, emailCampaign.getApprovedBy(), errorLog); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java index 9819131f9c6..46601de9024 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java @@ -18,11 +18,14 @@ */ package org.apache.fineract.infrastructure.campaigns.email.service; +import java.util.HashMap; import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage; +import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonQuery; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; public interface EmailCampaignWritePlatformService { @@ -44,4 +47,6 @@ public interface EmailCampaignWritePlatformService { void sendEmailMessage() throws JobExecutionException; + void insertDirectCampaignIntoEmailOutboundTable(Loan loan, EmailCampaign emailCampaign, HashMap campaignParams); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java index 519464405e2..c9abac45db3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java @@ -23,10 +23,9 @@ import java.util.Properties; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; -import org.apache.fineract.infrastructure.campaigns.email.EmailApiConstants; import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData; -import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfiguration; -import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfigurationRepository; +import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData; +import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,30 +37,33 @@ public final class EmailMessageJobEmailServiceImpl implements EmailMessageJobEmailService { private static final Logger LOG = LoggerFactory.getLogger(EmailMessageJobEmailServiceImpl.class); - private EmailConfigurationRepository emailConfigurationRepository; + private final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService; @Autowired - private EmailMessageJobEmailServiceImpl(final EmailConfigurationRepository emailConfigurationRepository) { - this.emailConfigurationRepository = emailConfigurationRepository; + private EmailMessageJobEmailServiceImpl(ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService) { + this.externalServicesReadPlatformService = externalServicesReadPlatformService; } @Override public void sendEmailWithAttachment(EmailMessageWithAttachmentData emailMessageWithAttachmentData) { + final SMTPCredentialsData smtpCredentialsData = this.externalServicesReadPlatformService.getSMTPCredentials(); try { JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl(); - javaMailSenderImpl.setHost(this.getGmailSmtpServer()); - javaMailSenderImpl.setPort(this.getGmailSmtpPort()); - javaMailSenderImpl.setUsername(this.getGmailSmtpUsername()); - javaMailSenderImpl.setPassword(this.getGmailSmtpPassword()); - javaMailSenderImpl.setJavaMailProperties(this.getJavaMailProperties()); + javaMailSenderImpl.setHost(smtpCredentialsData.getHost()); + javaMailSenderImpl.setPort(Integer.parseInt(smtpCredentialsData.getPort())); + javaMailSenderImpl.setUsername(smtpCredentialsData.getUsername()); + javaMailSenderImpl.setPassword(smtpCredentialsData.getPassword()); + javaMailSenderImpl + .setJavaMailProperties(this.getJavaMailProperties(smtpCredentialsData, javaMailSenderImpl.getJavaMailProperties())); MimeMessage mimeMessage = javaMailSenderImpl.createMimeMessage(); // use the true flag to indicate you need a multipart message MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); + mimeMessageHelper.setFrom(smtpCredentialsData.getFromEmail()); mimeMessageHelper.setTo(emailMessageWithAttachmentData.getTo()); - mimeMessageHelper.setText(emailMessageWithAttachmentData.getText()); + mimeMessageHelper.setText(emailMessageWithAttachmentData.getText(), true); mimeMessageHelper.setSubject(emailMessageWithAttachmentData.getSubject()); final List attachments = emailMessageWithAttachmentData.getAttachments(); if (attachments != null && attachments.size() > 0) { @@ -80,32 +82,18 @@ public void sendEmailWithAttachment(EmailMessageWithAttachmentData emailMessageW } - private String getGmailSmtpServer() { - final EmailConfiguration gmailSmtpServer = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_SERVER); - return (gmailSmtpServer != null) ? gmailSmtpServer.getValue() : null; - } - - private Integer getGmailSmtpPort() { - final EmailConfiguration gmailSmtpPort = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_PORT); - return (gmailSmtpPort != null) ? Integer.parseInt(gmailSmtpPort.getValue()) : null; - } - - private String getGmailSmtpUsername() { - final EmailConfiguration gmailSmtpUsername = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_USERNAME); - return (gmailSmtpUsername != null) ? gmailSmtpUsername.getValue() : null; - } - - private String getGmailSmtpPassword() { - final EmailConfiguration gmailSmtpPassword = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_PASSWORD); - return (gmailSmtpPassword != null) ? gmailSmtpPassword.getValue() : null; - } - - private Properties getJavaMailProperties() { - Properties properties = new Properties(); - properties.setProperty("mail.smtp.starttls.enable", "true"); - properties.setProperty("mail.smtp.auth", "true"); - properties.setProperty("mail.smtp.ssl.trust", this.getGmailSmtpServer()); - + private Properties getJavaMailProperties(SMTPCredentialsData smtpCredentialsData, Properties properties) { + properties.put("mail.smtp.starttls.enable", "true"); + properties.put("mail.transport.protocol", "smtp"); + properties.put("mail.smtp.auth", "true"); + properties.put("mail.smtp.ssl.trust", smtpCredentialsData.getHost()); + if (smtpCredentialsData.isUseTLS()) { + // Needs to disable startTLS if the port is 465 in order to send the email successfully when using the + // smtp.gmail.com as the host + if (smtpCredentialsData.getPort().equals("465")) { + properties.put("mail.smtp.starttls.enable", "false"); + } + } return properties; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java index ace77e5e67f..b997b536fe8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java @@ -20,6 +20,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -139,7 +140,7 @@ private SmsCampaign(final String campaignName, final Integer campaignType, final this.paramValue = paramValue; this.status = SmsCampaignStatus.PENDING.getValue(); this.message = message; - this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.submittedBy = submittedBy; this.recurrence = recurrence; LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()); @@ -280,7 +281,7 @@ public void activate(final AppUser currentUser, final DateTimeFormatter formatte throw new PlatformApiDataValidationException(dataValidationErrors); } - this.approvedOnDate = Date.from(activationLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.approvedOnDate = Date.from(activationLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.approvedBy = currentUser; this.status = SmsCampaignStatus.ACTIVE.getValue(); @@ -304,7 +305,7 @@ public void close(final AppUser currentUser, final DateTimeFormatter dateTimeFor this.lastTriggerDate = null; } this.closedBy = currentUser; - this.closureDate = Date.from(closureLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.closureDate = Date.from(closureLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.status = SmsCampaignStatus.CLOSED.getValue(); validateClosureDate(); } @@ -323,7 +324,7 @@ public void reactivate(final AppUser currentUser, final DateTimeFormatter dateTi throw new PlatformApiDataValidationException(dataValidationErrors); } - this.approvedOnDate = Date.from(reactivateLocalDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + this.approvedOnDate = Date.from(reactivateLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.status = SmsCampaignStatus.ACTIVE.getValue(); this.approvedBy = currentUser; this.closureDate = null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java index eaa623c0833..e7a0b9d9921 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java @@ -19,6 +19,8 @@ package org.apache.fineract.infrastructure.campaigns.sms.service; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.security.InvalidParameterException; import java.time.format.DateTimeFormatter; @@ -53,8 +55,6 @@ import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java index 00d7e84d944..a18298bb917 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.infrastructure.campaigns.sms.service; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; @@ -83,9 +86,6 @@ import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.useradministration.domain.AppUser; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java index 3174b455a62..b8e86d532c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiConstant.java @@ -31,5 +31,6 @@ private GlobalConfigurationApiConstant() { public static final String CONFIGURATION_RESOURCE_NAME = "globalConfiguration"; public static final String localeParamName = "locale"; public static final String dateFormatParamName = "dateFormat"; + public static final String STRING_VALUE = "stringValue"; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java index 40676ed2e08..668d6196a9f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationDataValidator.java @@ -43,9 +43,10 @@ public class GlobalConfigurationDataValidator { private final FromJsonHelper fromApiJsonHelper; - private static final Set UPDATE_CONFIGURATION_DATA_PARAMETERS = new HashSet<>(Arrays.asList( - GlobalConfigurationApiConstant.localeParamName, GlobalConfigurationApiConstant.dateFormatParamName, - GlobalConfigurationApiConstant.ENABLED, GlobalConfigurationApiConstant.VALUE, GlobalConfigurationApiConstant.DATE_VALUE)); + private static final Set UPDATE_CONFIGURATION_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(GlobalConfigurationApiConstant.localeParamName, GlobalConfigurationApiConstant.dateFormatParamName, + GlobalConfigurationApiConstant.ENABLED, GlobalConfigurationApiConstant.VALUE, GlobalConfigurationApiConstant.DATE_VALUE, + GlobalConfigurationApiConstant.STRING_VALUE)); @Autowired public GlobalConfigurationDataValidator(final FromJsonHelper fromApiJsonHelper) { @@ -81,6 +82,11 @@ public void validateForUpdate(final JsonCommand command) { baseDataValidator.reset().parameter(GlobalConfigurationApiConstant.DATE_VALUE).value(dateValue).notNull(); } + if (this.fromApiJsonHelper.parameterExists(GlobalConfigurationApiConstant.STRING_VALUE, element)) { + final String stringValue = this.fromApiJsonHelper.extractStringNamed(GlobalConfigurationApiConstant.STRING_VALUE, element); + baseDataValidator.reset().parameter(GlobalConfigurationApiConstant.STRING_VALUE).value(stringValue).notNull(); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java index a893b93739a..7e54b01b26d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/GlobalConfigurationPropertyData.java @@ -33,6 +33,7 @@ public class GlobalConfigurationPropertyData { private final Long value; @SuppressWarnings("unused") private final Date dateValue; + private String stringValue; @SuppressWarnings("unused") private final Long id; @SuppressWarnings("unused") @@ -41,22 +42,24 @@ public class GlobalConfigurationPropertyData { private final boolean trapDoor; public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value, final Date dateValue, - final String description, final boolean trapDoor) { + final String stringValue, final String description, final boolean trapDoor) { this.name = name; this.enabled = enabled; this.value = value; this.dateValue = dateValue; + this.stringValue = stringValue; this.id = null; this.description = description; this.trapDoor = trapDoor; } - public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value, Date dateValue, final Long id, - final String description, final boolean isTrapDoor) { + public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value, Date dateValue, + final String stringValue, final Long id, final String description, final boolean isTrapDoor) { this.name = name; this.enabled = enabled; this.value = value; this.dateValue = dateValue; + this.stringValue = stringValue; this.id = id; this.description = description; this.trapDoor = isTrapDoor; @@ -74,6 +77,10 @@ public Long getValue() { return this.value; } + public String getStringValue() { + return this.stringValue; + } + public Date getDateValue() { return this.dateValue; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index 4b791c92b7f..037f4d6a2f0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -75,6 +75,10 @@ public interface ConfigurationDomainService { boolean isSkippingMeetingOnFirstDayOfMonthEnabled(); + boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI(); + + boolean isPrincipalCompoundingDisabledForOverdueLoans(); + Long retreivePeroidInNumberOfDaysForSkipMeetingDate(); boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled(); @@ -97,5 +101,15 @@ public interface ConfigurationDomainService { boolean isFirstRepaymentDateAfterRescheduleAllowedOnHoliday(); - boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI(); + String getAccountMappingForPaymentType(); + + String getAccountMappingForCharge(); + + boolean isNextDayFixedDepositInterestTransferEnabledForPeriodEnd(); + + boolean retrievePivotDateConfig(); + + boolean isRelaxingDaysConfigForPivotDateEnabled(); + + Long retrieveRelaxingDaysConfigForPivotDate(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 321d640d6df..30426ef745d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -31,6 +31,7 @@ import org.apache.fineract.useradministration.domain.PermissionRepository; import org.apache.fineract.useradministration.exception.PermissionNotFoundException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -258,8 +259,13 @@ public boolean isFirstRepaymentDateAfterRescheduleAllowedOnHoliday() { } @Override - public boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI() { - return getGlobalConfigurationPropertyData("is-interest-to-be-appropriated-equally-when-greater-than-emi").isEnabled(); + public boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI() { + return getGlobalConfigurationPropertyData("is-interest-to-be-recovered-first-when-greater-than-emi").isEnabled(); + } + + @Override + public boolean isPrincipalCompoundingDisabledForOverdueLoans() { + return getGlobalConfigurationPropertyData("is-principal-compounding-disabled-for-overdue-loans").isEnabled(); } @Override @@ -343,6 +349,32 @@ public Integer retrieveOTPLiveTime() { return value; } + @Override + public boolean retrievePivotDateConfig() { + final String propertyName = "allow-backdated-transaction-before-interest-posting"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + return !property.isEnabled(); + + } + + @Override + public boolean isRelaxingDaysConfigForPivotDateEnabled() { + final String propertyName = "allow-backdated-transaction-before-interest-posting-date-for-days"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + return property.isEnabled(); + } + + @Override + public Long retrieveRelaxingDaysConfigForPivotDate() { + final String propertyName = "allow-backdated-transaction-before-interest-posting-date-for-days"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + if (property.getValue() == null) { + return Long.valueOf(0); + } + return property.getValue(); + } + + @Cacheable(value = "configByName", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#propertyName)") private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) { String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); String key = identifier + "_" + propertyName; @@ -363,4 +395,41 @@ public boolean isSubRatesEnabled() { } } + @Override + public String getAccountMappingForPaymentType() { + final String propertyName = "account-mapping-for-payment-type"; + String defaultValue = "Asset"; // 1 Stands for Account mapped from asset only + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + if (property.isEnabled()) { + String value = property.getStringValue(); + if (StringUtils.isBlank(value)) { + return defaultValue; + } + return value; + } + return defaultValue; + } + + @Override + public String getAccountMappingForCharge() { + final String propertyName = "account-mapping-for-charge"; + String defaultValue = "Income"; // 1 Stands for Account mapped from income only + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + if (property.isEnabled()) { + String value = property.getStringValue(); + if (StringUtils.isBlank(value)) { + return defaultValue; + } + return value; + } + return defaultValue; + } + + @Override + public boolean isNextDayFixedDepositInterestTransferEnabledForPeriodEnd() { + final String propertyName = "fixed-deposit-transfer-interest-next-day-for-period-end-posting"; + final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName); + return property.isEnabled(); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java index e6a3a2e3719..84e0283a4ea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java @@ -46,6 +46,9 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom { @Column(name = "date_value", nullable = true) private Date dateValue; + @Column(name = "string_value", nullable = true) + private String stringValue; + @Column(name = "description", nullable = true) private String description; @@ -57,16 +60,18 @@ protected GlobalConfigurationProperty() { this.enabled = false; this.value = null; this.dateValue = null; + this.stringValue = null; this.description = null; this.isTrapDoor = false; } public GlobalConfigurationProperty(final String name, final boolean enabled, final Long value, final Date dateValue, - final String description, final boolean isTrapDoor) { + final String stringValue, final String description, final boolean isTrapDoor) { this.name = name; this.enabled = enabled; this.value = value; this.dateValue = dateValue; + this.stringValue = stringValue; this.description = description; this.isTrapDoor = isTrapDoor; } @@ -83,6 +88,10 @@ public Date getDateValue() { return this.dateValue; } + public String getStringValue() { + return this.stringValue; + } + public Map update(final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(7); @@ -113,6 +122,13 @@ public Map update(final JsonCommand command) { this.dateValue = newDateValue; } + final String stringValueParamName = "stringValue"; + if (command.isChangeInStringParameterNamed(stringValueParamName, this.stringValue)) { + final String newStringValue = command.stringValueOfParameterNamed(stringValueParamName); + actualChanges.put(stringValueParamName, newStringValue); + this.stringValue = newStringValue; + } + final String passwordPropertyName = "force-password-reset-days"; if (this.name.equalsIgnoreCase(passwordPropertyName)) { if ((this.enabled == true && command.hasParameter(valueParamName) && (this.value == 0)) @@ -126,12 +142,12 @@ public Map update(final JsonCommand command) { } public static GlobalConfigurationProperty newSurveyConfiguration(final String name) { - return new GlobalConfigurationProperty(name, false, null, null, null, false); + return new GlobalConfigurationProperty(name, false, null, null, null, null, false); } public GlobalConfigurationPropertyData toData() { - return new GlobalConfigurationPropertyData(getName(), isEnabled(), getValue(), getDateValue(), this.getId(), this.description, - this.isTrapDoor); + return new GlobalConfigurationPropertyData(getName(), isEnabled(), getValue(), getDateValue(), getStringValue(), this.getId(), + this.description, this.isTrapDoor); } public String getName() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java index 65eaec1f7dd..dd7326ad5d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java @@ -52,7 +52,7 @@ public GlobalConfigurationData retrieveGlobalConfiguration(final boolean survey) this.context.authenticatedUser(); - String sql = "SELECT c.id, c.name, c.enabled, c.value, c.date_value, c.description, c.is_trap_door FROM c_configuration c "; + String sql = "SELECT c.id, c.name, c.enabled, c.value, c.date_value, c.description,c.string_value, c.is_trap_door FROM c_configuration c "; if (survey) { sql += " JOIN x_registered_table on x_registered_table.registered_table_name = c.name "; @@ -68,11 +68,11 @@ public GlobalConfigurationData retrieveGlobalConfiguration(final boolean survey) } @Override - public GlobalConfigurationPropertyData retrieveGlobalConfiguration(String name) { + public GlobalConfigurationPropertyData retrieveGlobalConfiguration(final String name) { this.context.authenticatedUser(); - final String sql = "SELECT c.id, c.name, c.enabled, c.value, c.date_value, c.description, c.is_trap_door FROM " + final String sql = "SELECT c.id, c.name, c.enabled, c.value, c.date_value, c.string_value, c.description, c.is_trap_door FROM " + "c_configuration c where c.name=? order by c.id"; final GlobalConfigurationPropertyData globalConfiguration = this.jdbcTemplate.queryForObject(sql, this.rm, new Object[] { name }); @@ -84,7 +84,7 @@ public GlobalConfigurationPropertyData retrieveGlobalConfiguration(Long configId this.context.authenticatedUser(); - final String sql = "SELECT c.id, c.name, c.enabled, c.value, c.date_value, c.description, c.is_trap_door FROM " + final String sql = "SELECT c.id, c.name, c.enabled, c.value, c.date_value, c.string_value ,c.description, c.is_trap_door FROM " + "c_configuration c where c.id=? order by c.id"; final GlobalConfigurationPropertyData globalConfiguration = this.jdbcTemplate.queryForObject(sql, this.rm, new Object[] { configId }); @@ -102,10 +102,11 @@ public GlobalConfigurationPropertyData mapRow(final ResultSet rs, @SuppressWarni final boolean enabled = rs.getBoolean("enabled"); final Long value = rs.getLong("value"); final Date dateValue = rs.getDate("date_value"); + final String stringValue = rs.getString("string_value"); final String description = rs.getString("description"); final Long id = rs.getLong("id"); final boolean isTrapDoor = rs.getBoolean("is_trap_door"); - return new GlobalConfigurationPropertyData(name, enabled, value, dateValue, id, description, isTrapDoor); + return new GlobalConfigurationPropertyData(name, enabled, value, dateValue, stringValue, id, description, isTrapDoor); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java index 8bde32705b2..c66cda977ab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java @@ -83,7 +83,7 @@ public SMTPCredentialsData extractData(final ResultSet rs) throws SQLException, if (ExternalServicesConstants.SMTP_USERNAME.equalsIgnoreCase(name)) { username = value; - } else if (ExternalServicesConstants.SMTP_PORT.equalsIgnoreCase(port)) { + } else if (ExternalServicesConstants.SMTP_PORT.equalsIgnoreCase(name)) { port = value; } else if (ExternalServicesConstants.SMTP_PASSWORD.equalsIgnoreCase(name)) { password = value; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java index c22ec9b0a1b..9d9f265dd4a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java @@ -26,6 +26,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.MonthDay; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Date; @@ -417,7 +418,7 @@ public Date dateValueOfParameterNamed(final String parameterName) { if (localDate == null) { return null; } - return Date.from(localDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()); + return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); } public boolean isChangeInStringParameterNamed(final String parameterName, final String existingValue) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaLocalDateAdapter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/LocalDateAdapter.java similarity index 90% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaLocalDateAdapter.java rename to fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/LocalDateAdapter.java index 2fced22a091..b2dccfea12b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JodaLocalDateAdapter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/LocalDateAdapter.java @@ -28,10 +28,10 @@ import java.time.temporal.ChronoField; /** - * Serializer for Joda Time {@link LocalDate} that returns the date in array format to match previous Jackson + * Serializer for Java Local Time {@link LocalDate} that returns the date in array format to match previous Jackson * functionality. */ -public class JodaLocalDateAdapter implements JsonSerializer { +public class LocalDateAdapter implements JsonSerializer { @Override @SuppressWarnings("unused") diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/LocalTimeAdapter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/LocalTimeAdapter.java new file mode 100644 index 00000000000..cb061e4dac6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/LocalTimeAdapter.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.api; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.time.LocalTime; + +/** + * Serializer for Java Local Time {@link LocalTime} that returns the time in array format to match previous Jackson + * functionality. + */ +public class LocalTimeAdapter implements JsonSerializer { + + @Override + @SuppressWarnings("unused") + public JsonElement serialize(final LocalTime src, final Type typeOfSrc, final JsonSerializationContext context) { + JsonArray array = null; + if (src != null) { + array = new JsonArray(); + array.add(new JsonPrimitive(src.getHour())); + array.add(new JsonPrimitive(src.getMinute())); + array.add(new JsonPrimitive(src.getSecond())); + } + return array; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java index 42a93667ba5..26e032ae4f7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.infrastructure.core.boot; -import org.apache.fineract.notification.config.MessagingConfiguration; +import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; @@ -26,33 +26,24 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportResource; -import org.springframework.context.annotation.PropertySource; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.transaction.annotation.EnableTransactionManagement; /** - * Base Spring Configuration with what's common to all Configuration subclasses. - * - * Notably the EnableAutoConfiguration excludes relevant for (and often adjusted when upgrading versions of) Spring - * Boot, the "old" (pre. Spring Boot & MariaDB4j) fineract appContext.xml which all configurations need, and the - * web.xml successor WebXmlConfiguration. - * - * Should NOT include Configuration related to embedded Tomcat, data sources, and MariaDB4j (because those differ in the - * subclasses). + * Base Spring Configuration. Excludes autoconfiguration for those things we want to manually configure. */ + @Configuration -@Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class, MessagingConfiguration.class, - WebTwoFactorXmlConfiguration.class }) -@ImportResource({ "classpath*:META-INF/spring/appContext.xml" }) -@PropertySource(value = "classpath:META-INF/spring/jdbc.properties") @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, FlywayAutoConfiguration.class, GsonAutoConfiguration.class, JdbcTemplateAutoConfiguration.class }) -@EnableWebSecurity @EnableTransactionManagement +@EnableWebSecurity +@EnableConfigurationProperties({ FineractProperties.class }) +@ComponentScan(basePackages = "org.apache.fineract.**") public abstract class AbstractApplicationConfiguration { } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/EmbeddedTomcatWithSSLConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/EmbeddedTomcatWithSSLConfiguration.java deleted file mode 100644 index 8f5d26c05b7..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/EmbeddedTomcatWithSSLConfiguration.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import org.apache.catalina.connector.Connector; -import org.apache.commons.io.FileUtils; -import org.apache.coyote.http11.Http11NioProtocol; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.servlet.server.ServletWebServerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; - -@Configuration -public class EmbeddedTomcatWithSSLConfiguration { - - // https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/howto.html#howto-enable-multiple-connectors-in-tomcat - - @Bean - public ServletWebServerFactory servletContainer() { - TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); - tomcat.setContextPath(getContextPath()); - tomcat.addAdditionalTomcatConnectors(createSslConnector()); - return tomcat; - } - - private String getContextPath() { - return "/fineract-provider"; - } - - protected Connector createSslConnector() { - Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); - Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler(); - try { - File keystore = getFile(getKeystore()); - connector.setScheme("https"); - connector.setSecure(true); - connector.setPort(getHTTPSPort()); - protocol.setSSLEnabled(true); - protocol.setKeystoreFile(keystore.getAbsolutePath()); - protocol.setKeystorePass(getKeystorePass()); - return connector; - } catch (IOException ex) { - throw new IllegalStateException("can't access keystore: [" + "keystore" + "] or truststore: [" + "keystore" + "]", ex); - } - } - - protected int getHTTPSPort() { - // TODO This shouldn't be hard-coded here, but configurable - return 8443; - } - - protected String getKeystorePass() { - return "openmf"; - } - - protected Resource getKeystore() { - return new ClassPathResource("/keystore.jks"); - } - - public File getFile(Resource resource) throws IOException { - try { - return resource.getFile(); - } catch (IOException e) { - // Uops.. OK, try again (below) - } - - try { - URL url = resource.getURL(); - /** - * // If this creates filenames that are too long on Win, // then could just use resource.getFilename(), // - * even though not unique, real risk prob. min.bon String tempDir = System.getProperty("java.io.tmpdir"); - * tempDir = tempDir + "/" + getClass().getSimpleName() + "/"; String path = url.getPath(); String uniqName - * = path.replace("file:/", "").replace('!', '_'); String tempFullPath = tempDir + uniqName; - **/ - // instead of File.createTempFile(prefix?, suffix?); - File targetFile = new File(resource.getFilename()); - long len = resource.contentLength(); - if (!targetFile.exists() || targetFile.length() != len) { // Only - // copy - // new - // files - FileUtils.copyURLToFile(url, targetFile); - } - return targetFile; - } catch (IOException e) { - // Uops.. erm, give up: - throw new IOException("Cannot obtain a File for Resource: " + resource.toString(), e); - } - - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/JDBCDriverConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/JDBCDriverConfig.java deleted file mode 100644 index 91a5f794a6a..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/JDBCDriverConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot; - -import javax.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Service; - -@Service -public class JDBCDriverConfig { - - private static final String DRIVER_CLASS_PROPERTYNAME = "DRIVERCLASS_NAME"; - private static final String PROTOCOL_PROPERTYNAME = "PROTOCOL"; - private static final String SUBPROTOCOL_PROPERTYNAME = "SUB_PROTOCOL"; - - private String driverClassName; - private String protocol; - private String subProtocol; - - @Autowired - ApplicationContext context; - - @PostConstruct - protected void init() { - Environment environment = context.getEnvironment(); - driverClassName = environment.getProperty(DRIVER_CLASS_PROPERTYNAME); - protocol = environment.getProperty(PROTOCOL_PROPERTYNAME); - subProtocol = environment.getProperty(SUBPROTOCOL_PROPERTYNAME); - } - - public String getDriverClassName() { - return this.driverClassName; - } - - public String constructProtocol(String schemaServer, String schemaServerPort, String schemaName, String schemaConnectionParameters) { - StringBuilder sb = new StringBuilder(protocol).append(":").append(subProtocol).append("://").append(schemaServer).append(":") - .append(schemaServerPort).append('/').append(schemaName); - if (schemaConnectionParameters != null && !schemaConnectionParameters.isEmpty()) { - sb.append('?').append(schemaConnectionParameters); - } - return sb.toString(); - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java index 05ee0cbef08..6bea4704b75 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java @@ -46,7 +46,8 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { LOG.info("Found Swagger UI at {}", fullPathToSwaggerUiJs); String fullPathToSwaggerUi = fullPathToSwaggerUiJs.substring(0, fullPathToSwaggerUiJs.lastIndexOf("/") + 1); - final String[] swaggerResourceLocations = { "classpath:/static/swagger-ui/", "classpath:" + fullPathToSwaggerUi }; + final String[] swaggerResourceLocations = { "classpath:/static/swagger-ui/", "classpath:fineract.yaml", + "classpath:" + fullPathToSwaggerUi }; registry.addResourceHandler("/swagger-ui/**").addResourceLocations(swaggerResourceLocations); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlConfiguration.java deleted file mode 100644 index 5b46a003363..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot; - -import com.sun.jersey.spi.spring.container.servlet.SpringServlet; -import org.apache.fineract.infrastructure.core.filters.ResponseCorsFilter; -import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; - -/** - * This Configuration replaces what formerly was in web.xml. - * - * @see #howto-convert-an-existing-application-to-spring-boot - */ -@Configuration -@Profile("basicauth") -public class WebXmlConfiguration { - - @Autowired - private TenantAwareBasicAuthenticationFilter basicAuthenticationProcessingFilter; - - @Bean - public ServletRegistrationBean jersey() { - ServletRegistrationBean jerseyServletRegistration = new ServletRegistrationBean(); - jerseyServletRegistration.setServlet(new SpringServlet()); - jerseyServletRegistration.addUrlMappings("/api/v1/*"); - jerseyServletRegistration.setName("jersey-servlet"); - jerseyServletRegistration.setLoadOnStartup(1); - jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true"); - jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters", - ResponseCorsFilter.class.getName()); - jerseyServletRegistration.addInitParameter("com.sun.jersey.config.feature.DisableWADL", "true"); - // debugging for development: - // jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", - // LoggingFilter.class.getName()); - return jerseyServletRegistration; - } - - @Bean - public FilterRegistrationBean filterRegistrationBean() { - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); - filterRegistrationBean.setFilter(basicAuthenticationProcessingFilter); - filterRegistrationBean.setEnabled(false); - return filterRegistrationBean; - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java deleted file mode 100644 index 6e558e86731..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot; - -import com.sun.jersey.spi.spring.container.servlet.SpringServlet; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.web.servlet.DispatcherServlet; - -/** - * This Configuration replaces what formerly was in web.xml. Beans are loaded only when "oauth" Profile is enabled. - * - * @see #howto-convert-an-existing-application-to-spring-boot - */ -@Configuration -@Profile("oauth") -public class WebXmlOauthConfiguration { - - @Bean - public ServletRegistrationBean jersey() { - ServletRegistrationBean jerseyServletRegistration = new ServletRegistrationBean(); - jerseyServletRegistration.setServlet(new SpringServlet()); - jerseyServletRegistration.addUrlMappings("/api/v1/*"); - jerseyServletRegistration.setName("jersey-servlet"); - jerseyServletRegistration.setLoadOnStartup(1); - jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true"); - // jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters", - // ResponseCorsFilter.class.getName()); - jerseyServletRegistration.addInitParameter("com.sun.jersey.config.feature.DisableWADL", "true"); - // debugging for development: - // jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", - // LoggingFilter.class.getName()); - return jerseyServletRegistration; - } - - @Bean - public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) { - ServletRegistrationBean registrationBean = new ServletRegistrationBean(dispatcherServlet); - registrationBean.addUrlMappings("/api/oauth/token"); - return registrationBean; - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jDataSourceConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jDataSourceConfiguration.java deleted file mode 100644 index f1992fd3ddc..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jDataSourceConfiguration.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot.db; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import ch.vorburger.mariadb4j.springframework.MariaDB4jSpringService; - -@Configuration -public class MariaDB4jDataSourceConfiguration extends DataSourceConfiguration { - - @Bean - public MariaDB4jSetupService mariaDB4jSetUp() { - return new MariaDB4jSetupService(mariaDB4j().getDB()); - } - - @Bean - public MariaDB4jSpringService mariaDB4j() { - MariaDB4jSpringService mariaDB4jSpringService = new MariaDB4jSpringService(); - mariaDB4jSpringService.setDefaultBaseDir("build/db/bin"); - mariaDB4jSpringService.setDefaultDataDir("build/db/data"); - return mariaDB4jSpringService; - } - - @Override - // NOT @Bean @Override dataSourceProperties() - doesn't work :( - protected DataSourceProperties getProperties() { - DataSourceProperties p = super.getProperties(); - String dbName = mariaDB4jSetUp().getTenantDBName(); - // Do not use p.setUrl(mariaDB4j().getConfiguration().getURL(dbName)); - // Because TenantDataSourcePortFixService needs separate - // host/port/db/uid/pwd: - // (DataSourceProperties getUrl() creates the correct JDBC URL from it) - // This intentionally overrides any fineract.datasource.* settings, because - // in this configuration, logically the mariaDB4j settings take - // precedence: - p.setHost("localhost"); - p.setPort(mariaDB4j().getConfiguration().getPort()); - p.setDBName(dbName); - // TODO p.setUsername(mariaDB4j().getConfiguration().getUsername()); - // TODO p.setPassword(mariaDB4j().getConfiguration().getPassword()); - return p; - } - -} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jSetupService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jSetupService.java deleted file mode 100644 index 877dbc27cd6..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/db/MariaDB4jSetupService.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot.db; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -import org.springframework.beans.factory.annotation.Autowired; - -import ch.vorburger.exec.ManagedProcessException; -import ch.vorburger.mariadb4j.DB; - -public class MariaDB4jSetupService { - - private DB db; - - @Autowired - public MariaDB4jSetupService(DB db) { - this.db = db; - } - - @PostConstruct - protected void setUpDBs() throws ManagedProcessException { - db.createDB(getTenantDBName()); - db.createDB("fineract_default"); - // Note that we don't need to initialize the DBs, because - // the TenantDatabaseUpgradeService will do this in just a moment. - } - - public String getTenantDBName() { - return "fineract_tenants"; - } - - @PreDestroy - protected void stop() throws ManagedProcessException { - db = null; - } -} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CacheConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CacheConfig.java new file mode 100644 index 00000000000..e6117fa68c5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CacheConfig.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import java.time.Duration; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.spi.CachingProvider; +import org.ehcache.config.builders.CacheConfigurationBuilder; +import org.ehcache.config.builders.ExpiryPolicyBuilder; +import org.ehcache.config.builders.ResourcePoolsBuilder; +import org.ehcache.jsr107.Eh107Configuration; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CacheConfig { + + @Bean + public JCacheCacheManager ehCacheManager() { + JCacheCacheManager jCacheCacheManager = new JCacheCacheManager(); + jCacheCacheManager.setCacheManager(getCustomCacheManager()); + return jCacheCacheManager; + } + + private CacheManager getCustomCacheManager() { + CachingProvider provider = Caching.getCachingProvider(); + CacheManager cacheManager = provider.getCacheManager(); + + javax.cache.configuration.Configuration defaultTemplate = Eh107Configuration.fromEhcacheCacheConfiguration( + CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(10000)) + .withExpiry(ExpiryPolicyBuilder.noExpiration()).build()); + + cacheManager.createCache("users", defaultTemplate); + cacheManager.createCache("usersByUsername", defaultTemplate); + cacheManager.createCache("tenantsById", defaultTemplate); + cacheManager.createCache("offices", defaultTemplate); + cacheManager.createCache("officesForDropdown", defaultTemplate); + cacheManager.createCache("officesById", defaultTemplate); + cacheManager.createCache("charges", defaultTemplate); + cacheManager.createCache("funds", defaultTemplate); + cacheManager.createCache("code_values", defaultTemplate); + cacheManager.createCache("codes", defaultTemplate); + cacheManager.createCache("hooks", defaultTemplate); + cacheManager.createCache("tfConfig", defaultTemplate); + + javax.cache.configuration.Configuration accessTokenTemplate = Eh107Configuration.fromEhcacheCacheConfiguration( + CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(10000)) + .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofHours(2))).build()); + + cacheManager.createCache("userTFAccessToken", accessTokenTemplate); + + return cacheManager; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CompatibilityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CompatibilityConfig.java new file mode 100644 index 00000000000..a33a0191f69 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CompatibilityConfig.java @@ -0,0 +1,151 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.util.Properties; +import javax.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +@ConditionalOnExpression("#{ systemEnvironment['fineract_tenants_driver'] != null }") +@Deprecated // NOTE: this will be removed in one of the next releases (probably around version 1.7.x or 1.8.x) +public class CompatibilityConfig { + + private static final Logger LOG = LoggerFactory.getLogger(CompatibilityConfig.class); + + @Autowired + ApplicationContext context; + + @PostConstruct + public void init() { + Environment environment = context.getEnvironment(); + + LOG.warn("===============================================================================================\n"); + LOG.warn("You are using a deprecated tenant DB configuration:\n"); + LOG.warn("- Env var 'fineract_tenants_driver': {}", + environment.getProperty("fineract_tenants_driver")); + LOG.warn("- Env var 'fineract_tenants_url': {}", + environment.getProperty("fineract_tenants_url")); + LOG.warn("- Env var 'fineract_tenants_uid': {}", + environment.getProperty("fineract_tenants_uid")); + LOG.warn("- Env var 'fineract_tenants_pwd': ****\n"); + LOG.warn("The preferred way to configure the tenant DB is now via these environment variables:\n"); + LOG.warn("- Env var 'FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME': {}", + environment.getProperty("FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME")); + LOG.warn("- Env var 'FINERACT_HIKARI_JDBC_URL': {}", + environment.getProperty("FINERACT_HIKARI_JDBC_URL")); + LOG.warn("- Env var 'FINERACT_HIKARI_USERNAME': {}", + environment.getProperty("FINERACT_HIKARI_USERNAME")); + LOG.warn("- Env var 'FINERACT_HIKARI_PASSWORD': ****"); + LOG.warn("- Env var 'FINERACT_HIKARI_MINIMUM_IDLE': {}", + environment.getProperty("FINERACT_HIKARI_MINIMUM_IDLE")); + LOG.warn("- Env var 'FINERACT_HIKARI_MAXIMUM_POOL_SIZE': {}", + environment.getProperty("FINERACT_HIKARI_MAXIMUM_POOL_SIZE")); + LOG.warn("- Env var 'FINERACT_HIKARI_IDLE_TIMEOUT': {}", + environment.getProperty("FINERACT_HIKARI_IDLE_TIMEOUT")); + LOG.warn("- Env var 'FINERACT_HIKARI_CONNECTION_TIMEOUT': {}", + environment.getProperty("FINERACT_HIKARI_CONNECTION_TIMEOUT")); + LOG.warn("- Env var 'FINERACT_HIKARI_TEST_QUERY': {}", + environment.getProperty("FINERACT_HIKARI_TEST_QUERY")); + LOG.warn("- Env var 'FINERACT_HIKARI_AUTO_COMMIT': {}", + environment.getProperty("FINERACT_HIKARI_AUTO_COMMIT")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES")); + LOG.warn("- Env var 'FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION': {}", + environment.getProperty("FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION")); + LOG.warn("===============================================================================================\n"); + } + + @Bean(destroyMethod = "close") + public HikariDataSource hikariTenantDataSource(HikariConfig hc) { + return new HikariDataSource(hc); + } + + @Bean + public HikariConfig hikariConfig() { + Environment environment = context.getEnvironment(); + HikariConfig hc = new HikariConfig(); + + hc.setDriverClassName(environment.getProperty("fineract_tenants_driver")); + hc.setJdbcUrl(environment.getProperty("fineract_tenants_url")); + hc.setUsername(environment.getProperty("fineract_tenants_uid")); + hc.setPassword(environment.getProperty("fineract_tenants_pwd")); + hc.setMinimumIdle(3); + hc.setMaximumPoolSize(10); + hc.setIdleTimeout(60000); + hc.setConnectionTestQuery("SELECT 1"); + hc.setDataSourceProperties(dataSourceProperties()); + + return hc; + } + + // These are the properties for the all Tenants DB; the same configuration is also (hard-coded) in the + // TomcatJdbcDataSourcePerTenantService class --> + private Properties dataSourceProperties() { + Properties props = new Properties(); + + props.setProperty("cachePrepStmts", "true"); + props.setProperty("prepStmtCacheSize", "250"); + props.setProperty("prepStmtCacheSqlLimit", "2048"); + props.setProperty("useServerPrepStmts", "true"); + props.setProperty("useLocalSessionState", "true"); + props.setProperty("rewriteBatchedStatements", "true"); + props.setProperty("cacheResultSetMetadata", "true"); + props.setProperty("cacheServerConfiguration", "true"); + props.setProperty("elideSetAutoCommits", "true"); + props.setProperty("maintainTimeStats", "false"); + + // https://github.com/brettwooldridge/HikariCP/wiki/JDBC-Logging#mysql-connectorj + // TODO FINERACT-890: com.mysql.cj.log.Slf4JLogger + props.setProperty("logSlowQueries", "true"); + props.setProperty("dumpQueriesOnException", "true"); + + return props; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java new file mode 100644 index 00000000000..2b7f535a156 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -0,0 +1,131 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "fineract") +public class FineractProperties { + + private String nodeId; + + private FineractTenantProperties tenant; + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public FineractTenantProperties getTenant() { + return tenant; + } + + public void setTenant(FineractTenantProperties tenant) { + this.tenant = tenant; + } + + public static class FineractTenantProperties { + + private String host; + private Integer port; + private String username; + private String password; + private String parameters; + private String timezone; + private String identifier; + private String name; + private String description; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getParameters() { + return parameters; + } + + public void setParameters(String parameters) { + this.parameters = parameters; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/HikariCpConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/HikariCpConfig.java new file mode 100644 index 00000000000..8086e569cd9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/HikariCpConfig.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnExpression("#{ systemEnvironment['fineract_tenants_driver'] == null }") +public class HikariCpConfig { + + // TODO: we can get rid of this config class by defining "spring.hikariTenantDataSource.hikari.*" in + // "application.properties" and enabling auto-configuration + + @Bean + @ConfigurationProperties(prefix = "spring.datasource.hikari") + public HikariConfig hikariConfig() { + return new HikariConfig(); + } + + @Bean(destroyMethod = "close") + public DataSource hikariTenantDataSource(HikariConfig hikariConfig) { + return new HikariDataSource(hikariConfig); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JerseyConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JerseyConfig.java new file mode 100644 index 00000000000..b5d915cd74a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JerseyConfig.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.infrastructure.core.config; + +import javax.annotation.PostConstruct; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.Path; +import javax.ws.rs.ext.Provider; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ApplicationPath("/api/v1") +public class JerseyConfig extends ResourceConfig { + + private static final Logger LOG = LoggerFactory.getLogger(JerseyConfig.class); + + JerseyConfig() { + register(org.glassfish.jersey.media.multipart.MultiPartFeature.class); + property(ServerProperties.WADL_FEATURE_DISABLE, true); + } + + @Autowired + ApplicationContext appCtx; + + @PostConstruct + public void setup() { + appCtx.getBeansWithAnnotation(Path.class).values().forEach(component -> register(component.getClass())); + + appCtx.getBeansWithAnnotation(Provider.class).values().forEach(this::register); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java new file mode 100644 index 00000000000..6266d1eab82 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.apache.fineract.infrastructure.core.exceptionmapper.OAuth2ExceptionEntryPoint; +import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken; +import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter; +import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; +import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService; +import org.apache.fineract.infrastructure.security.vote.SelfServiceUserAccessVote; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.http.HttpMethod; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.vote.AuthenticatedVoter; +import org.springframework.security.access.vote.RoleVoter; +import org.springframework.security.access.vote.UnanimousBased; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.web.access.expression.WebExpressionVoter; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; + +@Configuration +@ConditionalOnProperty("fineract.security.oauth.enabled") +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter; + + @Autowired + private TenantAwareTenantIdentifierFilter tenantAwareTenantIdentifierFilter; + + @Autowired + private TenantAwareJpaPlatformUserDetailsService userDetailsService; + + @Autowired + private ServerProperties serverProperties; + + private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http // + .csrf().disable() // NOSONAR only creating a service that is used by non-browser clients + .antMatcher("/api/**").authorizeRequests() // + .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/echo").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() // + .antMatchers("/api/*/twofactor").fullyAuthenticated() // + .antMatchers("/api/**").access("isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')") // + .accessDecisionManager(accessDecisionManager()).and() // + .exceptionHandling().authenticationEntryPoint(new OAuth2ExceptionEntryPoint()).and() + .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter())) + .authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) // + .sessionManagement() // + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // + .and() // + .addFilterAfter(tenantAwareTenantIdentifierFilter, SecurityContextPersistenceFilter.class) // + .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class); // + + if (serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresSecure()); + } else { + http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresInsecure()); + } + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + private Converter authenticationConverter() { + return jwt -> { + try { + UserDetails user = userDetailsService.loadUserByUsername(jwt.getSubject()); + jwtGrantedAuthoritiesConverter.setAuthorityPrefix(""); + Collection authorities = jwtGrantedAuthoritiesConverter.convert(jwt); + return new FineractJwtAuthenticationToken(jwt, authorities, user); + } catch (UsernameNotFoundException ex) { + throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN), ex); + } + }; + } + + @Bean + public AccessDecisionManager accessDecisionManager() { + List> decisionVoters = Arrays.asList(new RoleVoter(), new AuthenticatedVoter(), + new WebExpressionVoter(), new SelfServiceUserAccessVote()); + + return new UnanimousBased(decisionVoters); + } + + @Bean + public FilterRegistrationBean tenantAwareTenantIdentifierFilterRegistration() throws Exception { + FilterRegistrationBean registration = new FilterRegistrationBean( + tenantAwareTenantIdentifierFilter); + registration.setEnabled(false); + return registration; + } + + @Bean + public FilterRegistrationBean twoFactorAuthenticationFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean( + twoFactorAuthenticationFilter); + registration.setEnabled(false); + return registration; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/PersistenceConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/PersistenceConfig.java new file mode 100644 index 00000000000..4b148120706 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/PersistenceConfig.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import org.apache.fineract.infrastructure.core.domain.AuditorAwareImpl; +import org.apache.fineract.infrastructure.openjpa.OpenJpaVendorAdapter; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.support.TransactionTemplate; + +@Configuration +@EnableJpaAuditing +@EnableJpaRepositories(basePackages = "org.apache.fineract.**.domain") +public class PersistenceConfig { + + @Autowired + DataSource routingDataSource; + + @Bean + @DependsOn("tenantDatabaseUpgradeService") + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(routingDataSource); + // NOTE: very important to set this explicitly when building Docker images with Jib; otherwise complains about + // duplicate resources + em.setPersistenceXmlLocation("classpath:META-INF/persistence.xml"); + em.setJpaVendorAdapter(new OpenJpaVendorAdapter()); + em.setPersistenceUnitName("jpa-pu"); + em.afterPropertiesSet(); + return em; + } + + @Bean + public JpaTransactionManager transactionManager(EntityManagerFactory emf) { + JpaTransactionManager jtm = new JpaTransactionManager(); + jtm.setEntityManagerFactory(emf); + return jtm; + } + + @Bean + public TransactionTemplate txTemplate(JpaTransactionManager jtm) { + TransactionTemplate tt = new TransactionTemplate(); + tt.setTransactionManager(jtm); + return tt; + } + + @Bean + public AuditorAware auditorAware() { + return new AuditorAwareImpl(); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java new file mode 100644 index 00000000000..ed9dce56868 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java @@ -0,0 +1,143 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter; +import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; +import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +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.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; + +@Configuration +@ConditionalOnProperty("fineract.security.basicauth.enabled") +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private TenantAwareJpaPlatformUserDetailsService userDetailsService; + + @Autowired + private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter; + + @Autowired + private ServerProperties serverProperties; + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http // + .csrf().disable() // NOSONAR only creating a service that is used by non-browser clients + .antMatcher("/api/**").authorizeRequests() // + .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/echo").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() // + .antMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() // + .antMatchers("/api/*/twofactor").fullyAuthenticated() // + .antMatchers("/api/**").access("isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')").and() // + .httpBasic() // + .authenticationEntryPoint(basicAuthenticationEntryPoint()) // + .and() // + .sessionManagement() // + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // + .and() // + .addFilterAfter(tenantAwareBasicAuthenticationFilter(), SecurityContextPersistenceFilter.class) // + .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class); // + + if (serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresSecure()); + } else { + http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresInsecure()); + } + } + + @Bean + public TenantAwareBasicAuthenticationFilter tenantAwareBasicAuthenticationFilter() throws Exception { + return new TenantAwareBasicAuthenticationFilter(authenticationManagerBean(), basicAuthenticationEntryPoint()); + } + + @Bean + public BasicAuthenticationEntryPoint basicAuthenticationEntryPoint() { + BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint(); + basicAuthenticationEntryPoint.setRealmName("Fineract Platform API"); + return basicAuthenticationEntryPoint; + } + + @Bean(name = "customAuthenticationProvider") + public DaoAuthenticationProvider authProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authProvider()); + auth.eraseCredentials(false); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public FilterRegistrationBean tenantAwareBasicAuthenticationFilterRegistration() + throws Exception { + FilterRegistrationBean registration = new FilterRegistrationBean( + tenantAwareBasicAuthenticationFilter()); + registration.setEnabled(false); + return registration; + } + + @Bean + public FilterRegistrationBean twoFactorAuthenticationFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean( + twoFactorAuthenticationFilter); + registration.setEnabled(false); + return registration; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityValidationConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityValidationConfig.java new file mode 100644 index 00000000000..6954d46e512 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityValidationConfig.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.infrastructure.core.config; + +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SecurityValidationConfig { + + @Value("${fineract.security.basicauth.enabled}") + private Boolean basicAuthEnabled; + + @Value("${fineract.security.oauth.enabled}") + private Boolean oauthEnabled; + + @PostConstruct + public void validate() { + // NOTE: avoid NPE if these values are not set + if (!Boolean.TRUE.equals(basicAuthEnabled) && !Boolean.TRUE.equals(oauthEnabled)) { + // NOTE: while we are already doing consistency checks we might as well cover this case; should not happen + // as defaults are set in application.properties + throw new IllegalArgumentException( + "No authentication scheme selected. Please decide if you want to use basic OR OAuth2 authentication."); + } + + if (Boolean.TRUE.equals(basicAuthEnabled) && Boolean.TRUE.equals(oauthEnabled)) { + throw new IllegalArgumentException( + "Too many authentication schemes selected. Please decide if you want to use basic OR OAuth2 authentication."); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java new file mode 100644 index 00000000000..ef6149fb5a1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import org.springframework.beans.factory.config.MethodInvokingFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.security.core.context.SecurityContextHolder; + +@Configuration +public class SpringConfig { + + @Bean + public SimpleApplicationEventMulticaster applicationEventMulticaster() { + SimpleApplicationEventMulticaster saem = new SimpleApplicationEventMulticaster(); + saem.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return saem; + } + + // The application events (for importing) rely on the inheritable thread local security context strategy + // This is NOT compatible with threadpools so if we use threadpools the below will need to be reworked + @Bean + public MethodInvokingFactoryBean methodInvokingFactoryBean() { + MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean(); + mifb.setTargetClass(SecurityContextHolder.class); + mifb.setTargetMethod("setStrategyName"); + mifb.setArguments("MODE_INHERITABLETHREADLOCAL"); + return mifb; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TomcatConnectorCustomizer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TomcatConnectorCustomizer.java new file mode 100644 index 00000000000..ef7576b4833 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TomcatConnectorCustomizer.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.core.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnClass(name = "org.apache.coyote.http11.Http11Nio2Protocol") +public class TomcatConnectorCustomizer implements WebServerFactoryCustomizer { + + @Override + public void customize(ConfigurableServletWebServerFactory factory) { + ((TomcatServletWebServerFactory) factory).setProtocol("org.apache.coyote.http11.Http11Nio2Protocol"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java index 8c648e4e6f1..60daaddaad1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java @@ -18,10 +18,9 @@ */ package org.apache.fineract.infrastructure.core.data; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; /** * @@ -53,6 +52,7 @@ public class ApiGlobalErrorResponse { * A list of zero or more of the actual reasons for the HTTP error should they be needed. Typically used to express * data validation errors with parameters passed to API. */ + private List errors = new ArrayList<>(); public static ApiGlobalErrorResponse unAuthenticated() { @@ -194,8 +194,7 @@ public ApiGlobalErrorResponse(final List errors) { this.errors = errors; } - @XmlElementWrapper(name = "errors") - @XmlElement(name = "errorResponse") + @JsonProperty("errors") public List getErrors() { return this.errors; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java index 9ae0fda4b53..9ff42b378d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/DefaultPlatformPasswordEncoder.java @@ -25,7 +25,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -@SuppressWarnings("deprecation") @Service(value = "applicationPasswordEncoder") @Scope("singleton") public class DefaultPlatformPasswordEncoder implements PlatformPasswordEncoder { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java index b07f5ca39ae..cfa1287bbda 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.infrastructure.core.domain; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; + /** * Holds Tenant's DB server connection connection details. */ @@ -181,4 +184,23 @@ public String toString() { } return sb.toString(); } + + public static String toJdbcUrl(String protocol, String host, String port, String db, String parameters) { + StringBuilder sb = new StringBuilder(protocol).append("://").append(host).append(":").append(port).append('/').append(db); + + if (!StringUtils.isEmpty(parameters)) { + sb.append('?').append(parameters); + } + + return sb.toString(); + } + + public static String toProtocol(DataSource dataSource) { + try { + String url = dataSource.getConnection().getMetaData().getURL(); + return url.substring(0, url.indexOf("://")); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/MySQLDictionaryCustom.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/MySQLDictionaryCustom.java deleted file mode 100644 index 434d76cd9cc..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/MySQLDictionaryCustom.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.domain; - -import java.sql.Connection; -import java.sql.SQLException; -import org.apache.openjpa.jdbc.sql.BooleanRepresentationFactory; -import org.apache.openjpa.jdbc.sql.MySQLDictionary; - -@SuppressWarnings("unchecked") -public class MySQLDictionaryCustom extends MySQLDictionary { - - public MySQLDictionaryCustom() { - - this.supportsSubselect = true; - this.booleanRepresentation = BooleanRepresentationFactory.BOOLEAN; - this.supportsGetGeneratedKeys = false; - this.allowsAliasInBulkClause = true; - this.useWildCardForCount = true; - } - - @Override - public void connectedConfiguration(Connection conn) throws SQLException { - super.connectedConfiguration(conn); - this.supportsSubselect = true; - this.supportsGetGeneratedKeys = false; - this.allowsAliasInBulkClause = true; - this.useWildCardForCount = true; - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OAuth2ExceptionEntryPoint.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OAuth2ExceptionEntryPoint.java new file mode 100644 index 00000000000..7b41234b672 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OAuth2ExceptionEntryPoint.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.infrastructure.core.exceptionmapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +public class OAuth2ExceptionEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) + throws IOException, ServletException { + + ApiGlobalErrorResponse errorResponse = ApiGlobalErrorResponse.unAuthenticated(); + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(response.getOutputStream(), errorResponse); + } catch (Exception e) { + throw new ServletException(e); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java index a3fad864db1..e0b4d230302 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java @@ -18,36 +18,36 @@ */ package org.apache.fineract.infrastructure.core.filters; -import com.sun.jersey.spi.container.ContainerRequest; -import com.sun.jersey.spi.container.ContainerResponse; -import com.sun.jersey.spi.container.ContainerResponseFilter; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; /** * Filter that returns a response with headers that allows for Cross-Origin Requests (CORs) to be performed against the * platform API. */ + +@Provider +@Component +@Scope("singleton") public class ResponseCorsFilter implements ContainerResponseFilter { @Override - public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) { - - final ResponseBuilder resp = Response.fromResponse(response.getResponse()); + public void filter(final ContainerRequestContext request, final ContainerResponseContext response) { - resp.header("Access-Control-Allow-Origin", "*") - // .header("Access-Control-Expose-Headers", - // "Fineract-Platform-TenantId") - .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.getHeaders().add("Access-Control-Allow-Origin", "*"); + // .header("Access-Control-Expose-Headers", + // "Fineract-Platform-TenantId") + response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - final String reqHead = request.getHeaderValue("Access-Control-Request-Headers"); + final String reqHead = request.getHeaders().getFirst("Access-Control-Request-Headers"); - if (null != reqHead && !reqHead.equals(null)) { - resp.header("Access-Control-Allow-Headers", reqHead); + if (null != reqHead && StringUtils.hasText(reqHead)) { + response.getHeaders().add("Access-Control-Allow-Headers", reqHead); } - - response.setResponse(resp.build()); - - return response; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java index f74a5370a4e..3e4579c5d0b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java @@ -46,11 +46,11 @@ public class DatatableCommandFromApiJsonDeserializer { * The parameters supported for this command. */ private final Set supportedParametersForCreate = new HashSet<>( - Arrays.asList("datatableName", "apptableName", "multiRow", "columns")); + Arrays.asList("datatableName", "entitySubType", "apptableName", "multiRow", "columns")); private final Set supportedParametersForCreateColumns = new HashSet<>( Arrays.asList("name", "type", "length", "mandatory", "code")); private final Set supportedParametersForUpdate = new HashSet<>( - Arrays.asList("apptableName", "changeColumns", "addColumns", "dropColumns")); + Arrays.asList("apptableName", "entitySubType", "changeColumns", "addColumns", "dropColumns")); private final Set supportedParametersForAddColumns = new HashSet<>( Arrays.asList("name", "type", "length", "mandatory", "after", "code")); private final Set supportedParametersForChangeColumns = new HashSet<>( @@ -122,6 +122,11 @@ public void validateForCreate(final String json) { final String apptableName = this.fromApiJsonHelper.extractStringNamed("apptableName", element); baseDataValidator.reset().parameter("apptableName").value(apptableName).notBlank().notExceedingLengthOf(50) .isOneOfTheseValues(this.supportedApptableNames); + + if ("m_client".equals(apptableName)) { + String entitySubType = this.fromApiJsonHelper.extractStringNamed("entitySubType", element); + baseDataValidator.reset().parameter("entitySubType").value(entitySubType).notBlank(); // Person or Entity + } final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : ""; final Boolean multiRow = this.fromApiJsonHelper.extractBooleanNamed("multiRow", element); @@ -171,6 +176,12 @@ public void validateForUpdate(final String json) { final String apptableName = this.fromApiJsonHelper.extractStringNamed("apptableName", element); baseDataValidator.reset().parameter("apptableName").value(apptableName).ignoreIfNull().notBlank() .isOneOfTheseValues(this.supportedApptableNames); + + if ("m_client".equals(apptableName)) { + String entitySubType = this.fromApiJsonHelper.extractStringNamed("entitySubType", element); + baseDataValidator.reset().parameter("entitySubType").value(entitySubType).notBlank(); // Person or Entity + } + final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : ""; final JsonArray changeColumns = this.fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java index b91456968cd..6c27caaef22 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java @@ -22,6 +22,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.time.LocalDate; +import java.time.LocalTime; import java.time.MonthDay; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -29,8 +30,9 @@ import java.util.Set; import org.apache.fineract.infrastructure.core.api.DateAdapter; import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter; -import org.apache.fineract.infrastructure.core.api.JodaLocalDateAdapter; import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter; +import org.apache.fineract.infrastructure.core.api.LocalDateAdapter; +import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter; import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy; import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; @@ -42,15 +44,6 @@ @Service public final class GoogleGsonSerializerHelper { - public Gson createGsonBuilder(final boolean prettyPrint) { - final GsonBuilder builder = new GsonBuilder(); - registerTypeAdapters(builder); - if (prettyPrint) { - builder.setPrettyPrinting(); - } - return builder.create(); - } - public Gson createGsonBuilderForPartialResponseFiltering(final boolean prettyPrint, final Set responseParameters) { final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters); @@ -99,9 +92,24 @@ public String serializedJsonFrom(final Gson serializer, final Object singleDataO return serializer.toJson(singleDataObject); } + public static GsonBuilder createGsonBuilder() { + return createGsonBuilder(false); + } + + public static GsonBuilder createGsonBuilder(final boolean prettyPrint) { + final GsonBuilder builder = new GsonBuilder(); + registerTypeAdapters(builder); + if (prettyPrint) { + builder.setPrettyPrinting(); + } + return builder; + } + public static void registerTypeAdapters(final GsonBuilder builder) { builder.registerTypeAdapter(java.util.Date.class, new DateAdapter()); - builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); + builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter()); + // NOTE: was missing, necessary for GSON serialization with JDK 17's restrictive module access + builder.registerTypeAdapter(LocalTime.class, new LocalTimeAdapter()); builder.registerTypeAdapter(ZonedDateTime.class, new JodaDateTimeAdapter()); builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index 53c6fada117..bd08a6c7677 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -25,6 +25,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -69,7 +70,7 @@ public static LocalDate getLocalDateOfTenant() { public static LocalDateTime getLocalDateTimeOfTenant() { final ZoneId zone = getDateTimeZoneOfTenant(); - LocalDateTime today = LocalDateTime.now(zone); + LocalDateTime today = LocalDateTime.now(zone).truncatedTo(ChronoUnit.SECONDS); return today; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java index 318355036b3..c795c662d6b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java @@ -73,15 +73,15 @@ public void sendDefinedEmail(EmailDetail emailDetails) { props.put("mail.smtp.auth", "true"); props.put("mail.debug", "true"); - try { - if (smtpCredentialsData.isUseTLS()) { - // Needs to disable startTLS if the port is 465 in order to send the email successfully when using the - // smtp.gmail.com as the host - if (smtpCredentialsData.getPort().equals("465")) { - props.put("mail.smtp.starttls.enable", "false"); - } - } + // these are the added lines + props.put("mail.smtp.starttls.enable", "true"); + // props.put("mail.smtp.ssl.enable", "true"); + + props.put("mail.smtp.socketFactory.port", Integer.parseInt(smtpCredentialsData.getPort())); + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.fallback", "true"); + try { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(smtpCredentialsData.getFromEmail()); // same email address used for the authentication message.setTo(emailDetails.getAddress()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java index 0ad18af0442..ddaaadf3414 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java @@ -28,7 +28,7 @@ public class PaginationHelper { public Page fetchPage(final JdbcTemplate jt, final String sqlCountRows, final String sqlFetchRows, final Object[] args, final RowMapper rowMapper) { - final List items = jt.query(sqlFetchRows, args, rowMapper); + final List items = jt.query(sqlFetchRows, rowMapper, args); // determine how many rows are available final int totalFilteredRecords = jt.queryForObject(sqlCountRows, Integer.class); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java index 877d2717d61..264f9e1adcc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java @@ -18,11 +18,15 @@ */ package org.apache.fineract.infrastructure.core.service; +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toJdbcUrl; +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol; + +import com.zaxxer.hikari.HikariConfig; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.sql.DataSource; -import org.apache.fineract.infrastructure.core.boot.JDBCDriverConfig; +import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection; import org.apache.fineract.infrastructure.security.service.TenantDetailsService; @@ -45,17 +49,18 @@ public class TenantDatabaseUpgradeService { private static final Logger LOG = LoggerFactory.getLogger(TenantDatabaseUpgradeService.class); + private final HikariConfig hikariConfig; private final TenantDetailsService tenantDetailsService; protected final DataSource tenantDataSource; + protected final FineractProperties fineractProperties; @Autowired - private JDBCDriverConfig driverConfig; - - @Autowired - public TenantDatabaseUpgradeService(final TenantDetailsService detailsService, - @Qualifier("hikariTenantDataSource") final DataSource dataSource) { + public TenantDatabaseUpgradeService(final HikariConfig hikariConfig, final TenantDetailsService detailsService, + @Qualifier("hikariTenantDataSource") final DataSource dataSource, final FineractProperties fineractProperties) { + this.hikariConfig = hikariConfig; this.tenantDetailsService = detailsService; this.tenantDataSource = dataSource; + this.fineractProperties = fineractProperties; } @PostConstruct @@ -66,11 +71,11 @@ public void upgradeAllTenants() { final FineractPlatformTenantConnection connection = tenant.getConnection(); if (connection.isAutoUpdateEnabled()) { - String connectionProtocol = driverConfig.constructProtocol(connection.getSchemaServer(), connection.getSchemaServerPort(), + String protocol = toProtocol(this.tenantDataSource); + String jdbcUrl = toJdbcUrl(protocol, connection.getSchemaServer(), connection.getSchemaServerPort(), connection.getSchemaName(), connection.getSchemaConnectionParameters()); DriverDataSource source = new DriverDataSource(Thread.currentThread().getContextClassLoader(), - driverConfig.getDriverClassName(), connectionProtocol, connection.getSchemaUsername(), - connection.getSchemaPassword()); + hikariConfig.getDriverClassName(), jdbcUrl, connection.getSchemaUsername(), connection.getSchemaPassword()); final Flyway flyway = Flyway.configure().dataSource(source).locations("sql/migrations/core_db").outOfOrder(true) .placeholderReplacement(false).configuration(Map.of("flyway.table", "schema_version")) // FINERACT-979 @@ -84,7 +89,7 @@ public void upgradeAllTenants() { flyway.repair(); flyway.migrate(); } catch (FlywayException e) { - String betterMessage = e.getMessage() + "; for Tenant DB URL: " + connectionProtocol + ", username: " + String betterMessage = e.getMessage() + "; for Tenant DB URL: " + jdbcUrl + ", username: " + connection.getSchemaUsername(); throw new FlywayException(betterMessage, e); } @@ -96,18 +101,25 @@ public void upgradeAllTenants() { * Initializes, and if required upgrades (using Flyway) the Tenant DB itself. */ private void upgradeTenantDB() { - String dbHostname = getEnvVar("FINERACT_DEFAULT_TENANTDB_HOSTNAME", "localhost"); - String dbPort = getEnvVar("FINERACT_DEFAULT_TENANTDB_PORT", "3306"); - String dbUid = getEnvVar("FINERACT_DEFAULT_TENANTDB_UID", "root"); - String dbPwd = getEnvVar("FINERACT_DEFAULT_TENANTDB_PWD", "mysql"); - String dbConnParams = getEnvVar("FINERACT_DEFAULT_TENANTDB_CONN_PARAMS", ""); - LOG.info("upgradeTenantDB: FINERACT_DEFAULT_TENANTDB_HOSTNAME = {}, FINERACT_DEFAULT_TENANTDB_PORT = {}", dbHostname, dbPort); + LOG.info("Upgrade tenant DB: {}:{}", fineractProperties.getTenant().getHost(), fineractProperties.getTenant().getPort()); + LOG.info("- fineract.tenant.username: {}", fineractProperties.getTenant().getUsername()); + LOG.info("- fineract.tenant.password: ****"); + LOG.info("- fineract.tenant.parameters: {}", fineractProperties.getTenant().getParameters()); + LOG.info("- fineract.tenant.timezone: {}", fineractProperties.getTenant().getTimezone()); + LOG.info("- fineract.tenant.description: {}", fineractProperties.getTenant().getDescription()); + LOG.info("- fineract.tenant.identifier: {}", fineractProperties.getTenant().getIdentifier()); + LOG.info("- fineract.tenant.name: {}", fineractProperties.getTenant().getName()); final Flyway flyway = Flyway.configure().dataSource(tenantDataSource).locations("sql/migrations/list_db").outOfOrder(true) // FINERACT-773 - .placeholders(Map.of("fineract_default_tenantdb_hostname", dbHostname, "fineract_default_tenantdb_port", dbPort, - "fineract_default_tenantdb_uid", dbUid, "fineract_default_tenantdb_pwd", dbPwd, - "fineract_default_tenantdb_conn_params", dbConnParams)) + .placeholders(Map.of("fineract.tenant.host", fineractProperties.getTenant().getHost(), "fineract.tenant.port", + fineractProperties.getTenant().getPort().toString(), "fineract.tenant.username", + fineractProperties.getTenant().getUsername(), "fineract.tenant.password", + fineractProperties.getTenant().getPassword(), "fineract.tenant.parameters", + fineractProperties.getTenant().getParameters(), "fineract.tenant.timezone", + fineractProperties.getTenant().getTimezone(), "fineract.tenant.description", + fineractProperties.getTenant().getDescription(), "fineract.tenant.identifier", + fineractProperties.getTenant().getIdentifier(), "fineract.tenant.name", fineractProperties.getTenant().getName())) .configuration(Map.of("flyway.table", "schema_version")) // FINERACT-979 .load(); @@ -119,14 +131,6 @@ private void upgradeTenantDB() { flyway.migrate(); } - private String getEnvVar(String name, String defaultValue) { - String value = System.getenv(name); - if (value == null) { - return defaultValue; - } - return value; - } - private void repairFlywayVersionSkip(DataSource source) { JdbcTemplate template = new JdbcTemplate(source); LOG.info("repairFlywayVersionSkip: Check whether the version table is in old format "); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java index 99a5b5ab5e9..8be95f24e99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TomcatJdbcDataSourcePerTenantService.java @@ -18,12 +18,14 @@ */ package org.apache.fineract.infrastructure.core.service; +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toJdbcUrl; +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol; + import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; -import org.apache.fineract.infrastructure.core.boot.JDBCDriverConfig; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection; import org.springframework.beans.factory.annotation.Autowired; @@ -43,7 +45,7 @@ public class TomcatJdbcDataSourcePerTenantService implements RoutingDataSourceSe private final DataSource tenantDataSource; @Autowired - private JDBCDriverConfig driverConfig; + private HikariConfig hikariConfig; @Autowired public TomcatJdbcDataSourcePerTenantService(final @Qualifier("hikariTenantDataSource") DataSource tenantDataSource) { @@ -77,21 +79,21 @@ public DataSource retrieveDataSource() { // creates the tenant data source for the oltp and report database private DataSource createNewDataSourceFor(final FineractPlatformTenantConnection tenantConnectionObj) { - String jdbcUrl = this.driverConfig.constructProtocol(tenantConnectionObj.getSchemaServer(), - tenantConnectionObj.getSchemaServerPort(), tenantConnectionObj.getSchemaName(), - tenantConnectionObj.getSchemaConnectionParameters()); + String protocol = toProtocol(this.tenantDataSource); + String jdbcUrl = toJdbcUrl(protocol, tenantConnectionObj.getSchemaServer(), tenantConnectionObj.getSchemaServerPort(), + tenantConnectionObj.getSchemaName(), tenantConnectionObj.getSchemaConnectionParameters()); HikariConfig config = new HikariConfig(); - config.setDriverClassName(this.driverConfig.getDriverClassName()); + config.setDriverClassName(hikariConfig.getDriverClassName()); config.setPoolName(tenantConnectionObj.getSchemaName() + "_pool"); config.setJdbcUrl(jdbcUrl); config.setUsername(tenantConnectionObj.getSchemaUsername()); config.setPassword(tenantConnectionObj.getSchemaPassword()); config.setMinimumIdle(tenantConnectionObj.getInitialSize()); config.setMaximumPoolSize(tenantConnectionObj.getMaxActive()); - config.setConnectionTestQuery("SELECT 1"); + config.setConnectionTestQuery(hikariConfig.getConnectionTestQuery()); config.setValidationTimeout(tenantConnectionObj.getValidationInterval()); - config.setAutoCommit(true); + config.setAutoCommit(hikariConfig.isAutoCommit()); // https://github.com/brettwooldridge/HikariCP/wiki/MBean-(JMX)-Monitoring-and-Management config.setRegisterMbeans(true); @@ -100,22 +102,7 @@ private DataSource createNewDataSourceFor(final FineractPlatformTenantConnection // These are the properties for each Tenant DB; the same configuration // is also in src/main/resources/META-INF/spring/hikariDataSource.xml // for the all Tenants DB --> - config.addDataSourceProperty("cachePrepStmts", "true"); - config.addDataSourceProperty("prepStmtCacheSize", "250"); - config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - config.addDataSourceProperty("useServerPrepStmts", "true"); - config.addDataSourceProperty("useLocalSessionState", "true"); - config.addDataSourceProperty("rewriteBatchedStatements", "true"); - config.addDataSourceProperty("cacheResultSetMetadata", "true"); - config.addDataSourceProperty("cacheServerConfiguration", "true"); - config.addDataSourceProperty("elideSetAutoCommits", "true"); - config.addDataSourceProperty("maintainTimeStats", "false"); - - // https://github.com/brettwooldridge/HikariCP/wiki/JDBC-Logging#mysql-connectorj - // TODO FINERACT-890: config.addDataSourceProperty("logger", - // "com.mysql.cj.log.Slf4JLogger"); - config.addDataSourceProperty("logSlowQueries", "true"); - config.addDataSourceProperty("dumpQueriesOnException", "true"); + config.setDataSourceProperties(hikariConfig.getDataSourceProperties()); return new HikariDataSource(config); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationAPI.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationAPI.java index df50ade02d1..1adf20c09ea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationAPI.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationAPI.java @@ -20,8 +20,6 @@ package org.apache.fineract.infrastructure.creditbureau.api; import com.google.gson.Gson; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataParam; import io.swagger.v3.oas.annotations.Parameter; import java.io.File; import java.io.InputStream; @@ -52,6 +50,8 @@ import org.apache.fineract.infrastructure.creditbureau.service.CreditReportReadPlatformService; import org.apache.fineract.infrastructure.creditbureau.service.CreditReportWritePlatformService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauConfigurationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauConfigurationWritePlatformServiceImpl.java index 81bfa9cc886..923a0f5efe1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauConfigurationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauConfigurationWritePlatformServiceImpl.java @@ -65,7 +65,7 @@ public CommandProcessingResult addCreditBureauConfiguration(Long creditBureauId, this.fromApiJsonDeserializer.validateForCreate(command.json(), creditBureauId); - final OrganisationCreditBureau orgcb = this.organisationCreditBureauRepository.getOne(creditBureauId); + final OrganisationCreditBureau orgcb = this.organisationCreditBureauRepository.getById(creditBureauId); final CreditBureauConfiguration cb_config = CreditBureauConfiguration.fromJson(command, orgcb); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauLoanProductMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauLoanProductMappingWritePlatformServiceImpl.java index 950b907b318..4b4a20b7a1a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauLoanProductMappingWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditBureauLoanProductMappingWritePlatformServiceImpl.java @@ -68,9 +68,9 @@ public CommandProcessingResult addCreditBureauLoanProductMapping(Long organisati final long lpid = command.longValueOfParameterNamed("loanProductId"); - final OrganisationCreditBureau orgcb = this.organisationCreditBureauRepository.getOne(organisationCreditBureauId); + final OrganisationCreditBureau orgcb = this.organisationCreditBureauRepository.getById(organisationCreditBureauId); - final LoanProduct lp = this.loanProductRepository.getOne(lpid); + final LoanProduct lp = this.loanProductRepository.getById(lpid); final CreditBureauLoanProductMapping cb_lp = CreditBureauLoanProductMapping.fromJson(command, orgcb, lp); @@ -88,7 +88,7 @@ public CommandProcessingResult updateCreditBureauLoanProductMapping(JsonCommand final Long mappingid = command.longValueOfParameterNamed("creditbureauLoanProductMappingId"); final boolean isActive = command.booleanPrimitiveValueOfParameterNamed("isActive"); - final CreditBureauLoanProductMapping cblpmapping = this.creditBureauLoanProductMappingRepository.getOne(mappingid); + final CreditBureauLoanProductMapping cblpmapping = this.creditBureauLoanProductMappingRepository.getById(mappingid); cblpmapping.setIs_active(isActive); this.creditBureauLoanProductMappingRepository.saveAndFlush(cblpmapping); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(cblpmapping.getId()).build(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformService.java index 082e21e0edb..5f6dda6490c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformService.java @@ -18,10 +18,10 @@ */ package org.apache.fineract.infrastructure.creditbureau.service; -import com.sun.jersey.core.header.FormDataContentDisposition; import java.io.File; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; public interface CreditReportWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformServiceImpl.java index e2a2c398787..4ea67c2a3d2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/CreditReportWritePlatformServiceImpl.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.infrastructure.creditbureau.service; -import com.sun.jersey.core.header.FormDataContentDisposition; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -47,6 +46,7 @@ import org.apache.fineract.infrastructure.creditbureau.domain.TokenRepositoryWrapper; import org.apache.fineract.infrastructure.creditbureau.serialization.CreditBureauTokenCommandFromApiJsonDeserializer; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaSystemException; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/OrganisationCreditBureauWritePlatflormServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/OrganisationCreditBureauWritePlatflormServiceImpl.java index f17dc2097dc..a283744419d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/OrganisationCreditBureauWritePlatflormServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/OrganisationCreditBureauWritePlatflormServiceImpl.java @@ -58,7 +58,7 @@ public CommandProcessingResult addOrganisationCreditBureau(Long creditBureauId, this.context.authenticatedUser(); this.fromApiJsonDeserializer.validateForCreate(command.json(), creditBureauId); - final CreditBureau creditBureau = this.creditBureauRepository.getOne(creditBureauId); + final CreditBureau creditBureau = this.creditBureauRepository.getById(creditBureauId); final OrganisationCreditBureau organisationCreditBureau = OrganisationCreditBureau.fromJson(command, creditBureau); @@ -78,7 +78,7 @@ public CommandProcessingResult updateCreditBureau(JsonCommand command) { final boolean isActive = command.booleanPrimitiveValueOfParameterNamed("isActive"); - final OrganisationCreditBureau orgcb = organisationCreditBureauRepository.getOne(creditbureauID); + final OrganisationCreditBureau orgcb = organisationCreditBureauRepository.getById(creditbureauID); orgcb.setIsActive(isActive); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformService.java index d422f3813aa..25ecbb3bd57 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformService.java @@ -18,11 +18,11 @@ */ package org.apache.fineract.infrastructure.creditbureau.service; -import com.sun.jersey.core.header.FormDataContentDisposition; import java.io.File; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.creditbureau.data.CreditBureauReportData; import org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauToken; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; public interface ThitsaWorksCreditBureauIntegrationWritePlatformService { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java index 355d37a94c0..926b4bbc90b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java @@ -24,7 +24,6 @@ import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.sun.jersey.core.header.FormDataContentDisposition; import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; @@ -56,6 +55,7 @@ import org.apache.fineract.infrastructure.creditbureau.domain.TokenRepositoryWrapper; import org.apache.fineract.infrastructure.creditbureau.serialization.CreditBureauTokenCommandFromApiJsonDeserializer; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java index dca344d44a2..4918f77d483 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java @@ -27,6 +27,7 @@ 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.HashMap; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -229,7 +230,7 @@ public String getDatatable(@PathParam("datatable") @Parameter(description = "dat + "datatables/extra_family_details/1?order=`Date of Birth` desc\n" + "\n" + "\n" + "datatables/extra_client_details/1?genericResultSet=true") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.GetDataTablesAppTableIdResponse.class))) }) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = HashMap.class))) }) public String getDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable, @PathParam("apptableId") @Parameter(description = "apptableId") final Long apptableId, @QueryParam("order") @Parameter(description = "order") final String order, @Context final UriInfo uriInfo) { @@ -283,7 +284,7 @@ public String getDatatableManyEntry(@PathParam("datatable") final String datatab @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Create Entry in Data Table", description = "Adds a row to the data table.\n" + "\n" + "Note that the default datatable UI functionality converts any field name containing spaces to underscores when using the API. This means the field name \"Business Description\" is considered the same as \"Business_Description\". So you shouldn't have both \"versions\" in any data table.") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.PostDataTablesAppTableIdRequest.class))) + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = String.class)), description = "{\n \"BusinessDescription\": \"Livestock sales\",\n \"Comment\": \"First comment made\",\n \"Education_cv\": \"Primary\",\n \"Gender_cd\": 6,\n \"HighestRatePaid\": 8.5,\n \"NextVisit\": \"01 October 2012\",\n \"YearsinBusiness\": 5,\n \"dateFormat\": \"dd MMMM yyyy\",\n \"locale\": \"en\"\n}") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.PostDataTablesAppTableIdResponse.class))) }) public String createDatatableEntry(@PathParam("datatable") @Parameter(description = "datatable") final String datatable, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java index b33d1332697..379d51fe4a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java @@ -41,26 +41,41 @@ private GetDataTablesResponse() { } @Schema(example = "m_client") - public String appTableName; + public String applicationTableName; @Schema(example = "extra_client_details") - public String datatableName; - public List column; + public String registeredTableName; + public List columnHeaderData; } @Schema(description = "PostDataTablesRequest") public static final class PostDataTablesRequest { - private PostDataTablesRequest() { + private PostDataTablesRequest() {} + + static final class PostColumnHeaderData { + private PostColumnHeaderData() {} + + @Schema(required = true, example = "DOB") + public String name; + @Schema(required = true, example = "String", description = "Any of them: Boolean | Date | DateTime | Decimal | Dropdown | Number | String | Text") + public String type; + @Schema(example = "Gender", description = "Used in Code Value fields. Column name becomes: code_cd_name. Mandatory if using type Dropdown, otherwise an error is returned.") + public String code; + @Schema(example = "true", description = "Defaults to false") + public Boolean mandatory; + @Schema(example = "1653", description = "Length of the text field. Mandatory if type String is used, otherwise an error is returned.") + public Long length; } - @Schema(example = "m_client") - public String applicationTableName; - @Schema(example = "extra_client_details") - public String registeredTableName; + @Schema(required = true, example = "m_client") + public String apptableName; + @Schema(required = true, example = "extra_client_details") + public String datatableName; @Schema(required = false, description = "Allows to create multiple entries in the Data Table. Optional, defaults to false. If this property is not provided Data Table will allow only one entry.", example = "true") public boolean multiRow; - public List columnHeaderData; + @Schema(required = true) + public List columns; } @Schema(description = "PostDataTablesResponse") diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java index a3dd2e87ce5..0718e8b14db 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java @@ -45,6 +45,7 @@ import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.useradministration.domain.AppUser; +import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -97,7 +98,9 @@ public Response runReport(@PathParam("reportName") @Parameter(description = "rep @Context final UriInfo uriInfo, @DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = IS_SELF_SERVICE_USER_REPORT_PARAMETER) final boolean isSelfServiceUserReport) { - final MultivaluedMap queryParams = uriInfo.getQueryParameters(); + MultivaluedMap queryParams = new MultivaluedStringMap(); + queryParams.putAll(uriInfo.getQueryParameters()); + final boolean parameterType = ApiParameterHelper.parameterType(queryParams); checkUserPermissionForReport(reportName, parameterType); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.java index fb149ce244b..8e22cbbca4e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/DatatableData.java @@ -31,17 +31,20 @@ public final class DatatableData implements Serializable { @SuppressWarnings("unused") private final String registeredTableName; @SuppressWarnings("unused") + private final String entitySubType; + @SuppressWarnings("unused") private final List columnHeaderData; - public static DatatableData create(final String applicationTableName, final String registeredTableName, + public static DatatableData create(final String applicationTableName, final String registeredTableName, final String entitySubType, final List columnHeaderData) { - return new DatatableData(applicationTableName, registeredTableName, columnHeaderData); + return new DatatableData(applicationTableName, registeredTableName, entitySubType, columnHeaderData); } - private DatatableData(final String applicationTableName, final String registeredTableName, + private DatatableData(final String applicationTableName, final String registeredTableName, final String entitySubType, final List columnHeaderData) { this.applicationTableName = applicationTableName; this.registeredTableName = registeredTableName; + this.entitySubType = entitySubType; this.columnHeaderData = columnHeaderData; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java index 96629b646d2..32023ab4e54 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java @@ -33,7 +33,6 @@ public final class ResultsetColumnHeaderData implements Serializable { private final Long columnLength; private final String columnDisplayType; private final boolean isColumnNullable; - @SuppressWarnings("unused") private final boolean isColumnPrimaryKey; private final List columnValues; @@ -183,13 +182,17 @@ private boolean isVarchar() { } private boolean isAnyInteger() { - return isInt() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt() || isLong(); + return isInt() || isInteger() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt() || isLong(); } private boolean isInt() { return "int".equalsIgnoreCase(this.columnType); } + private boolean isInteger() { + return "integer".equalsIgnoreCase(this.columnType); + } + private boolean isSmallInt() { return "smallint".equalsIgnoreCase(this.columnType); } @@ -227,6 +230,14 @@ public Long getColumnLength() { return this.columnLength; } + public boolean getIsColumnNullable() { + return isColumnNullable; + } + + public boolean getIsColumnPrimaryKey() { + return isColumnPrimaryKey; + } + public String getColumnDisplayType() { return this.columnDisplayType; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java index f348b38037a..079982bd625 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java @@ -35,7 +35,7 @@ public interface ReadWriteNonCoreDataService { void registerDatatable(JsonCommand command); @PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')") - void registerDatatable(String dataTableName, String applicationTableName); + void registerDatatable(String dataTableName, String applicationTableName, String entitySubType); @PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')") void registerDatatable(JsonCommand command, String permissionTable); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index 4a43db0c90b..fcf6e580c5a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -149,8 +149,8 @@ public List retrieveDatatableNames(final String appTable) { } // PERMITTED datatables - final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists" - + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + final String sql = "select application_table_name, registered_table_name, entity_subtype " + " from x_registered_table " + + " where exists" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id" + " where ur.appuser_id = " + this.context.authenticatedUser().getId() + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " @@ -162,10 +162,11 @@ public List retrieveDatatableNames(final String appTable) { while (rs.next()) { final String appTableName = rs.getString("application_table_name"); final String registeredDatatableName = rs.getString("registered_table_name"); + final String entitySubType = rs.getString("entity_subtype"); final List columnHeaderData = this.genericDataService .fillResultsetColumnHeaders(registeredDatatableName); - datatables.add(DatatableData.create(appTableName, registeredDatatableName, columnHeaderData)); + datatables.add(DatatableData.create(appTableName, registeredDatatableName, entitySubType, columnHeaderData)); } return datatables; @@ -176,8 +177,8 @@ public DatatableData retrieveDatatable(final String datatable) { // PERMITTED datatables SQLInjectionValidator.validateSQLInput(datatable); - final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists" - + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + final String sql = "select application_table_name, registered_table_name, entity_subtype" + " from x_registered_table " + + " where exists" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id" + " where ur.appuser_id = " + this.context.authenticatedUser().getId() + " and registered_table_name='" + datatable + "'" + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " @@ -189,10 +190,11 @@ public DatatableData retrieveDatatable(final String datatable) { while (rs.next()) { final String appTableName = rs.getString("application_table_name"); final String registeredDatatableName = rs.getString("registered_table_name"); + final String entitySubType = rs.getString("entity_subtype"); final List columnHeaderData = this.genericDataService .fillResultsetColumnHeaders(registeredDatatableName); - datatableData = DatatableData.create(appTableName, registeredDatatableName, columnHeaderData); + datatableData = DatatableData.create(appTableName, registeredDatatableName, entitySubType, columnHeaderData); } return datatableData; @@ -204,12 +206,12 @@ private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) { @Transactional @Override - public void registerDatatable(final String dataTableName, final String applicationTableName) { + public void registerDatatable(final String dataTableName, final String applicationTableName, final String entitySubType) { Integer category = DataTableApiConstant.CATEGORY_DEFAULT; final String permissionSql = this.getPermissionSql(dataTableName); - this.registerDataTable(applicationTableName, dataTableName, category, permissionSql); + this.registerDataTable(applicationTableName, dataTableName, entitySubType, category, permissionSql); } @@ -219,12 +221,13 @@ public void registerDatatable(final JsonCommand command) { final String applicationTableName = this.getTableName(command.getUrl()); final String dataTableName = this.getDataTableName(command.getUrl()); + final String entitySubType = command.stringValueOfParameterNamed("entitySubType"); Integer category = this.getCategory(command); this.dataTableValidator.validateDataTableRegistration(command.json()); final String permissionSql = this.getPermissionSql(dataTableName); - this.registerDataTable(applicationTableName, dataTableName, category, permissionSql); + this.registerDataTable(applicationTableName, dataTableName, entitySubType, category, permissionSql); } @@ -233,27 +236,29 @@ public void registerDatatable(final JsonCommand command) { public void registerDatatable(final JsonCommand command, final String permissionSql) { final String applicationTableName = this.getTableName(command.getUrl()); final String dataTableName = this.getDataTableName(command.getUrl()); + final String entitySubType = command.stringValueOfParameterNamed("entitySubType"); Integer category = this.getCategory(command); this.dataTableValidator.validateDataTableRegistration(command.json()); - this.registerDataTable(applicationTableName, dataTableName, category, permissionSql); + this.registerDataTable(applicationTableName, dataTableName, entitySubType, category, permissionSql); } @Transactional - private void registerDataTable(final String applicationTableName, final String dataTableName, final Integer category, - final String permissionsSql) { + private void registerDataTable(final String applicationTableName, final String dataTableName, final String entitySubType, + final Integer category, final String permissionsSql) { validateAppTable(applicationTableName); validateDatatableName(dataTableName); assertDataTableExists(dataTableName); Map paramMap = new HashMap<>(3); - final String registerDatatableSql = "insert into x_registered_table (registered_table_name, application_table_name,category) values ( :dataTableName, :applicationTableName, :category)"; + final String registerDatatableSql = "insert into x_registered_table (registered_table_name, application_table_name, entity_subtype, category) values ( :dataTableName, :applicationTableName, :entitySubType ,:category)"; paramMap.put("dataTableName", dataTableName); paramMap.put("applicationTableName", applicationTableName); + paramMap.put("entitySubType", entitySubType); paramMap.put("category", category); try { @@ -568,6 +573,7 @@ public CommandProcessingResult createDatatable(final JsonCommand command) { final JsonElement element = this.fromJsonHelper.parse(command.json()); final JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element); datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element); + String entitySubType = this.fromJsonHelper.extractStringNamed("entitySubType", element); final String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element); Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element); @@ -623,7 +629,7 @@ public CommandProcessingResult createDatatable(final JsonCommand command) { sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;"); this.jdbcTemplate.execute(sqlBuilder.toString()); - registerDatatable(datatableName, apptableName); + registerDatatable(datatableName, apptableName, entitySubType); registerColumnCodeMapping(codeMappings); } catch (final JpaSystemException | DataIntegrityViolationException e) { final Throwable realCause = e.getCause(); @@ -888,6 +894,7 @@ public void updateDatatable(final String datatableName, final JsonCommand comman final JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element); final JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element); final String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element); + final String entitySubType = this.fromJsonHelper.extractStringNamed("entitySubType", element); validateDatatableName(datatableName); int rowCount = getRowCount(datatableName); @@ -899,6 +906,12 @@ public void updateDatatable(final String datatableName, final JsonCommand comman final boolean isConstraintApproach = this.configurationDomainService.isConstraintApproachEnabledForDatatables(); + if (!StringUtils.isBlank(entitySubType)) { + String updateLegalFormSQL = "update `x_registered_table` SET `entity_subtype`='" + entitySubType + + "' WHERE registered_table_name = '" + datatableName + "'"; + this.jdbcTemplate.execute(updateLegalFormSQL); + } + if (!StringUtils.isBlank(apptableName)) { validateAppTable(apptableName); @@ -929,7 +942,7 @@ public void updateDatatable(final String datatableName, final JsonCommand comman this.jdbcTemplate.execute(sqlBuilder.toString()); deregisterDatatable(datatableName); - registerDatatable(datatableName, apptableName); + registerDatatable(datatableName, apptableName, entitySubType); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java index 6e53cf5490e..3efb32ae033 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/DocumentManagementApiResource.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.infrastructure.documentmanagement.api; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataBodyPart; -import com.sun.jersey.multipart.FormDataParam; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -57,6 +54,9 @@ import org.apache.fineract.infrastructure.documentmanagement.service.DocumentReadPlatformService; import org.apache.fineract.infrastructure.documentmanagement.service.DocumentWritePlatformService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -126,20 +126,16 @@ public String retrieveAllDocuments(@Context final UriInfo uriInfo, public String createDocument(@PathParam("entityType") @Parameter(description = "entityType") final String entityType, @PathParam("entityId") @Parameter(description = "entityId") final Long entityId, @HeaderParam("Content-Length") @Parameter(description = "Content-Length") final Long fileSize, - @FormDataParam("file") @Parameter(description = "file") final InputStream inputStream, - @FormDataParam("file") @Parameter(description = "file") final FormDataContentDisposition fileDetails, - @FormDataParam("file") @Parameter(description = "file") final FormDataBodyPart bodyPart, - @FormDataParam("name") @Parameter(description = "name") final String name, - @FormDataParam("description") @Parameter(description = "description") final String description) { + @FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails, + @FormDataParam("file") final FormDataBodyPart bodyPart, @FormDataParam("name") final String name, + @FormDataParam("description") final String description) { // TODO: stop reading from stream after max size is reached to protect against malicious clients // TODO: need to extract the actual file type and determine if they are permissible fileUploadValidator.validate(fileSize, inputStream, fileDetails, bodyPart); - final DocumentCommand documentCommand = new DocumentCommand(null, null, entityType, entityId, name, fileDetails.getFileName(), fileSize, bodyPart.getMediaType().toString(), description, null); - final Long documentId = this.documentWritePlatformService.createDocument(documentCommand, inputStream); return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(documentId, null)); @@ -158,11 +154,9 @@ public String updateDocument(@PathParam("entityType") @Parameter(description = " @PathParam("entityId") @Parameter(description = "entityId") final Long entityId, @PathParam("documentId") @Parameter(description = "documentId") final Long documentId, @HeaderParam("Content-Length") @Parameter(description = "Content-Length") final Long fileSize, - @FormDataParam("file") @Parameter(description = "file") final InputStream inputStream, - @FormDataParam("file") @Parameter(description = "file") final FormDataContentDisposition fileDetails, - @FormDataParam("file") @Parameter(description = "file") final FormDataBodyPart bodyPart, - @FormDataParam("name") @Parameter(description = "name") final String name, - @FormDataParam("description") @Parameter(description = "description") final String description) { + @FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails, + @FormDataParam("file") final FormDataBodyPart bodyPart, @FormDataParam("name") final String name, + @FormDataParam("description") final String description) { final Set modifiedParams = new HashSet<>(); modifiedParams.add("name"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/FileUploadValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/FileUploadValidator.java index 299c35c36fc..430a34e6f03 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/FileUploadValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/FileUploadValidator.java @@ -18,10 +18,10 @@ */ package org.apache.fineract.infrastructure.documentmanagement.api; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataBodyPart; import java.io.InputStream; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.springframework.stereotype.Component; /** diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java index 9242086ab5d..ea88993d8eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.infrastructure.documentmanagement.api; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataBodyPart; -import com.sun.jersey.multipart.FormDataParam; import java.io.IOException; import java.io.InputStream; import java.util.Base64; @@ -50,6 +47,9 @@ import org.apache.fineract.infrastructure.documentmanagement.service.ImageWritePlatformService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.client.data.ClientData; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.java index e9f03e5c6fd..03647104ff4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepositoryUtils.java @@ -18,9 +18,10 @@ */ package org.apache.fineract.infrastructure.documentmanagement.contentrepository; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; -import java.util.Random; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.domain.Base64EncodedImage; @@ -31,7 +32,7 @@ public final class ContentRepositoryUtils { - private static final Random random = new Random(); + private static final SecureRandom random = new SecureRandom(); private ContentRepositoryUtils() {} @@ -187,6 +188,9 @@ public static void validateClientImageNotEmpty(final String imageFileName) { /** * Generate a random String. */ + + @SuppressFBWarnings(value = { + "DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once") public static String generateRandomString() { final String characters = "abcdefghijklmnopqrstuvwxyz123456789"; // length is a random number between 5 to 16 diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java index 312a59ef014..7e2b9eeb628 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java @@ -55,6 +55,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; @@ -64,11 +65,11 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.fineract.infrastructure.gcm.GcmConstants; @@ -88,7 +89,7 @@ public class Sender { */ protected static final int MAX_BACKOFF_DELAY = 1024000; - protected final Random random = new Random(); + private static final SecureRandom random = new SecureRandom(); protected static final Logger LOG = Logger.getLogger(Sender.class.getName()); private final String key; @@ -168,6 +169,9 @@ public final void setReadTimeout(int readTimeout) { * @throws IOException * if message could not be sent. */ + + @SuppressFBWarnings(value = { + "DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once") public Result send(Message message, String to, int retries) throws IOException { int attempt = 0; Result result; @@ -316,6 +320,8 @@ public Result sendNoRetry(Message message, String to) throws IOException { * @throws IOException * if message could not be sent. */ + @SuppressFBWarnings(value = { + "DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once") public MulticastResult send(Message message, List regIds, int retries) throws IOException { int attempt = 0; MulticastResult multicastResult; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java index c3d9ab55961..ee1e370a218 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java @@ -21,10 +21,10 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDate; +import java.time.ZoneId; import java.util.Collection; import java.util.Date; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.RoutingDataSource; import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationData; import org.apache.fineract.infrastructure.gcm.exception.DeviceRegistrationNotFoundException; @@ -75,7 +75,7 @@ public DeviceRegistrationData mapRow(final ResultSet rs, @SuppressWarnings("unus final String clientName = rs.getString("clientName"); ClientData clientData = ClientData.instance(clientId, clientName); return DeviceRegistrationData.instance(id, clientData, registrationId, - Date.from(updatedOnDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant())); + Date.from(updatedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant())); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java index dc1d2da1763..3de6c638294 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.gcm.service; import java.io.IOException; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -109,7 +110,7 @@ public void sendNotifiaction(Long clientId, List smsList) { if (res.getSuccess() != null && res.getSuccess() > 0) { smsMessage.setStatusType(SmsMessageStatusType.SENT.getValue()); smsMessage.setDeliveredOnDate( - Date.from(DateUtils.getLocalDateOfTenant().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant())); + Date.from(DateUtils.getLocalDateOfTenant().atStartOfDay(ZoneId.systemDefault()).toInstant())); } else if (res.getFailure() != null && res.getFailure() > 0) { smsMessage.setStatusType(SmsMessageStatusType.FAILED.getValue()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java index c11deea3468..381e36997df 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java @@ -40,6 +40,8 @@ private HookApiConstants() { public static final String elasticSearchTemplateName = "Elastic Search"; + public static final String httpSMSTemplateName = "Message Gateway"; + public static final String smsTemplateName = "SMS Bridge"; public static final String payloadURLName = "Payload URL"; @@ -68,6 +70,8 @@ private HookApiConstants() { public static final String templateNameParamName = "templateName"; + public static final String SMSProviderIdParamName = "SMS Provider Id"; + static final Set RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(nameParamName, displayNameParamName, templateIdParamName, isActiveParamName, configParamName, eventsParamName, templateNameParamName)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java index 23d57096f0f..5f637ba71be 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.hooks.processor; import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.elasticSearchTemplateName; +import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.httpSMSTemplateName; import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsTemplateName; import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.webTemplateName; @@ -47,6 +48,8 @@ public HookProcessor getProcessor(final Hook hook) { processor = this.applicationContext.getBean("webHookProcessor", WebHookProcessor.class); } else if (templateName.equals(elasticSearchTemplateName)) { processor = this.applicationContext.getBean("elasticSearchHookProcessor", ElasticSearchHookProcessor.class); + } else if (templateName.equals(httpSMSTemplateName)) { + processor = this.applicationContext.getBean("messageGatewayHookProcessor", MessageGatewayHookProcessor.class); } else { processor = null; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.java new file mode 100644 index 00000000000..4cfd89fe5bf --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.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.infrastructure.hooks.processor; + +import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.SMSProviderIdParamName; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; +import org.apache.fineract.infrastructure.hooks.domain.Hook; +import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration; +import org.apache.fineract.infrastructure.sms.domain.SmsMessage; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; +import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.template.domain.Template; +import org.apache.fineract.template.domain.TemplateRepository; +import org.apache.fineract.template.service.TemplateMergeService; +import org.apache.fineract.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MessageGatewayHookProcessor implements HookProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(MessageGatewayHookProcessor.class); + + private final ClientRepositoryWrapper clientRepository; + private final TemplateRepository templateRepository; + private final TemplateMergeService templateMergeService; + + private final SmsMessageRepository smsMessageRepository; + private final SmsMessageScheduledJobService smsMessageScheduledJobService; + + @Autowired + public MessageGatewayHookProcessor(ClientRepositoryWrapper clientRepository, TemplateRepository templateRepository, + TemplateMergeService templateMergeService, SmsMessageRepository smsMessageRepository, + SmsMessageScheduledJobService smsMessageScheduledJobService) { + this.clientRepository = clientRepository; + this.templateRepository = templateRepository; + this.templateMergeService = templateMergeService; + this.smsMessageRepository = smsMessageRepository; + this.smsMessageScheduledJobService = smsMessageScheduledJobService; + } + + @Override + public void process(final Hook hook, @SuppressWarnings("unused") final AppUser appUser, final String payload, final String entityName, + final String actionName, final String tenantIdentifier, final String authToken) throws IOException { + + final Set config = hook.getHookConfig(); + + Integer SMSProviderId = null; + + for (final HookConfiguration conf : config) { + final String fieldName = conf.getFieldName(); + if (fieldName.equals(SMSProviderIdParamName)) { + SMSProviderId = Integer.parseInt(conf.getFieldValue()); + } + } + + String templateName = entityName + "_" + actionName; + + // 1 : find template via mapper using entity and action + Template template; + List