FINERACT-1470: Fineract release plugin

This commit is contained in:
Aleksandar Vidakovic
2022-05-04 15:11:53 +02:00
committed by Aleksandar Vidakovic
parent b185937962
commit 40eb547a5c
160 changed files with 4954 additions and 638 deletions
@@ -16,6 +16,7 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Build the image
run: ./gradlew :fineract-provider:clean :fineract-provider:jibDockerBuild -x test
- name: Start the stack
+1
View File
@@ -16,6 +16,7 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Build the image
run: ./gradlew :fineract-provider:clean :fineract-provider:jibDockerBuild -x test
- name: Start the stack
+1
View File
@@ -45,6 +45,7 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@859c33240bd026ce8d5f711f5adcc65c2f8eafc1
- name: Verify PostgreSQL connection
+1
View File
@@ -44,6 +44,7 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@859c33240bd026ce8d5f711f5adcc65c2f8eafc1
- name: Verify MariaDB connection
+1
View File
@@ -34,6 +34,7 @@ jobs:
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@859c33240bd026ce8d5f711f5adcc65c2f8eafc1
- name: Sonarqube
+4 -1
View File
@@ -21,4 +21,7 @@ bin/
fineract-provider/out/
fineract-provider/config/swagger/config.json
fineract-provider/config/swagger/fineract-input.yaml
licenses
licenses
**/.asciidoctor/images/*
**/.asciidoctor/diagram/*
**/images/diag-*.svg
-53
View File
@@ -457,59 +457,6 @@ Upgrades sometimes require package name changes. Changed code should ideally ha
Our `ClasspathHellDuplicatesCheckRuleTest` detects classes that appear in more than 1 JAR. If a version bump in [`build.gradle`](https://github.com/search?q=repo%3Aapache%2Ffineract+filename%3Abuild.gradle&type=Code&ref=advsearch&l=&l=) causes changes in transitives dependencies, then you may have to add related `exclude` to our [`dependencies.gradle`](https://github.com/apache/fineract/search?q=dependencies.gradle). Running `./gradlew dependencies` helps to understand what is required.
Releasing
---------
[How to Release Apache Fineract](https://cwiki.apache.org/confluence/x/DRwIB) documents the process how we make the source code that is available here in this Git repository into a binary release tar.gz available on http://fineract.apache.org.
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:
```
signing.gnupg.keyName=7890ABCD
signing.gnupg.passphrase=secret
```
IMPORTANT: Do not set your GPG secrets in one of the project gradle.properties and double check that you are not accidentally committing them to Git.
The release command would look then look like this:
```
./gradlew -Pfineract.release clean build
```
2. Another way to provide these parameters are via project parameters on the command line. A release command would then look like this:
```
./gradlew -Pfineract.release -Psigning.gnupg.keyName=7890ABCD -Psigning.gnupg.passphrase=secret clean build
```
NOTE: Let's assume your GPG key ID would be "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCD" then you have to use the last 8 characters (i. e. "7890ABCD") for the signing plugin property "signing.gnupg.keyName".
Above tasks will create the following files in folder build/distributions:
- 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.6.0-binary.tar.gz.asc
```
The output should look somewhat like this:
```
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) <aleks@apache.org>" [ultimate]
```
NOTE: All commands shown above are assuming that the current working directory is the project root folder.
More Information
============
More details of the project can be found at <https://cwiki.apache.org/confluence/display/FINERACT>.
+9 -158
View File
@@ -56,10 +56,9 @@ buildscript {
}
plugins {
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "org.barfuin.gradle.taskinfo" version "1.4.0"
id 'com.adarshr.test-logger' version '3.2.0'
id 'com.diffplug.spotless' version '6.5.0' apply false
id 'com.diffplug.spotless' version '6.5.1' apply false
id 'org.nosphere.apache.rat' version '0.7.1' apply false
id 'com.github.hierynomus.license' version '0.16.1' apply false
id 'com.github.jk1.dependency-license-report' version '2.1' apply false
@@ -68,7 +67,7 @@ plugins {
id 'org.springframework.boot' version '2.6.7' apply false
id 'net.ltgt.errorprone' version '2.0.2' apply false
id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.2.0' apply false
id 'com.gorylenko.gradle-git-properties' version '2.4.0' apply false
id 'com.gorylenko.gradle-git-properties' version '2.4.1' apply false
id 'org.asciidoctor.jvm.convert' version '3.3.2' apply false
id 'org.asciidoctor.jvm.pdf' version '3.3.2' apply false
id 'org.asciidoctor.jvm.epub' version '3.3.2' apply false
@@ -83,6 +82,8 @@ plugins {
id 'se.thinkcode.cucumber-runner' version '0.0.10' apply false
}
apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle"
description = '''\
Run as:
gradle clean bootRun'''
@@ -91,6 +92,8 @@ ext['groovy.version'] = '3.0.10'
ext['swaggerFile'] = "$rootDir/fineract-provider/build/classes/java/main/static/fineract.yaml".toString()
allprojects {
apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle"
group = 'org.apache.fineract'
jgitver {
@@ -103,7 +106,6 @@ allprojects {
mavenCentral()
}
apply plugin: 'io.spring.dependency-management'
apply plugin: 'com.adarshr.test-logger'
apply plugin: 'com.diffplug.spotless'
apply plugin: 'com.github.hierynomus.license'
@@ -111,158 +113,6 @@ allprojects {
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.19'
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.7'
mavenBom 'org.jetbrains.kotlin:kotlin-bom:1.6.21'
mavenBom 'org.junit:junit-bom:5.8.2'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.2.1'
mavenBom 'io.cucumber:cucumber-bom:7.3.2'
mavenBom 'io.netty:netty-bom:4.1.76.Final'
mavenBom 'org.mockito:mockito-bom:4.5.1'
mavenBom 'io.github.swagger2markup:swagger2markup-bom:1.3.4'
}
dependencies {
// We use fixed versions, instead of inheriting them from the Spring BOM, to be able to be on more recent ones.
// We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things.
// We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available.
dependency 'org.slf4j:slf4j-api:1.7.36'
dependency 'org.slf4j:slf4j-simple:1.7.36'
dependency 'org.slf4j:jcl-over-slf4j:1.7.36'
dependency 'org.slf4j:jul-to-slf4j:1.7.36'
dependency 'org.slf4j:log4j-over-slf4j:1.7.36'
dependency 'ch.qos.logback:logback-core:1.2.11'
dependency 'ch.qos.logback:logback-classic:1.2.11'
dependency 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10'
dependency 'com.google.guava:guava:31.1-jre'
dependency 'com.google.code.gson:gson:2.9.0'
dependency 'com.google.truth:truth:1.1.3'
dependency 'com.google.truth.extensions:truth-java8-extension:1.1.3'
dependency 'com.google.googlejavaformat:google-java-format:1.15.0'
dependency ('org.apache.commons:commons-email:1.5') {
exclude 'com.sun.mail:javax.mail'
exclude 'javax.activation:activation'
}
dependency 'commons-io:commons-io:2.11.0'
dependency 'com.github.librepdf:openpdf:1.3.27'
dependency ('org.mnode.ical4j:ical4j:3.2.2') {
exclude 'com.sun.mail:javax.mail'
}
dependency 'org.quartz-scheduler:quartz:2.3.2'
dependency 'com.amazonaws:aws-java-sdk-s3:1.12.205'
dependency 'org.ehcache:ehcache:3.10.0'
dependency 'com.github.spullara.mustache.java:compiler:0.9.10'
dependency 'com.jayway.jsonpath:json-path:2.7.0'
dependency 'org.apache.tika:tika-core:2.3.0'
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
dependency 'jakarta.management.j2ee:jakarta.management.j2ee-api:1.1.4'
dependency 'jakarta.jms:jakarta.jms-api:2.0.3'
dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.6' // Swagger needs exactly this version
dependency 'org.apache.bval:org.apache.bval.bundle:2.0.5'
dependency 'joda-time:joda-time:2.10.14'
dependency 'io.github.classgraph:classgraph:4.8.146'
dependency 'org.awaitility:awaitility:4.2.0'
dependency 'com.github.spotbugs:spotbugs-annotations:4.6.0'
dependency 'javax.cache:cache-api:1.1.1'
dependency 'org.mock-server:mockserver-junit-jupiter:5.13.2'
dependency 'org.webjars:webjars-locator-core:0.50'
dependency 'com.icegreen:greenmail-junit5:1.6.8'
dependency 'com.intuit.karate:karate-junit5:1.2.0.RC6'
// fineract client dependencies
dependency "com.squareup.retrofit2:retrofit:$retrofitVersion"
dependency "com.squareup.retrofit2:retrofit-mock:$retrofitVersion"
dependency "com.squareup.retrofit2:adapter-java8:$retrofitVersion"
dependency "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
dependency "com.squareup.retrofit2:adapter-rxjava3:$retrofitVersion"
dependency "com.squareup.retrofit2:adapter-guava:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-wire:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-jackson:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-simplexml:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-jaxb:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-java8:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-scalars:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-gson:$retrofitVersion"
dependency "com.squareup.retrofit2:converter-protobuf:$retrofitVersion"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.common:$oltuVersion"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltuVersion"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.httpclient4:$oltuVersion"
dependency "com.squareup.okhttp3:okhttp:$okhttpVersion"
dependency "com.squareup.okhttp3:okcurl:$okhttpVersion"
dependency "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
dependency "com.squareup.okhttp3:okhttp-apache:$okhttpVersion"
dependency "com.squareup.okhttp3:okhttp-android-support:$okhttpVersion"
dependency "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
dependency "com.squareup.okhttp3:okhttp-sse:$okhttpVersion"
dependency "io.gsonfire:gson-fire:1.8.5"
dependency "com.google.code.findbugs:jsr305:3.0.2"
dependency "commons-codec:commons-codec:1.15"
dependency "org.bouncycastle:bcpkix-jdk15to18:1.70"
dependency "org.bouncycastle:bcprov-jdk15to18:1.70"
dependency "org.projectlombok:lombok:1.18.24"
dependency 'org.springdoc:springdoc-openapi-ui:1.6.8'
dependency 'org.springdoc:springdoc-openapi-webmvc-core:1.6.8'
dependency 'org.springdoc:springdoc-openapi-common:1.6.8'
dependency 'org.springdoc:springdoc-openapi-security:1.6.8'
dependency 'org.springdoc:springdoc-openapi-javadoc:1.6.8'
dependency ('org.apache.activemq:activemq-broker:5.17.0') {
exclude 'javax.annotation:javax.annotation-api'
}
dependency 'io.swagger:swagger-annotations:1.6.6'
dependency 'io.swagger.core.v3:swagger-annotations:2.2.0'
dependency ('io.swagger.core.v3:swagger-jaxrs2:2.2.0') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency ('io.swagger.core.v3:swagger-core:2.2.0') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency "jakarta.annotation:jakarta.annotation-api:1.3.5"
dependency 'jakarta.activation:jakarta.activation-api:1.2.2'
dependency ('com.sun.mail:jakarta.mail:1.6.7') {
exclude 'com.sun.activation:jakarta.activation'
}
dependency ('jakarta.xml.bind:jakarta.xml.bind-api:2.3.3') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency ('org.liquibase:liquibase-core:4.9.1') {
exclude 'javax.xml.bind:jaxb-api'
}
dependency ('org.dom4j:dom4j:2.1.3') {
exclude 'relaxngDatatype:relaxngDatatype' // already in com.sun.xml.bind:jaxb-osgi:2.3.0.1
// FINERACT-940 && FINERACT-966 https://github.com/spotbugs/spotbugs/issues/1128
exclude 'xpp3:xpp3'
exclude 'pull-parser:pull-parser'
}
dependency 'org.owasp.esapi:esapi:2.3.0.0'
dependencySet(group: 'org.apache.poi', version: '5.2.2') {
entry 'poi'
entry 'poi-ooxml'
entry 'poi-ooxml-schemas'
}
dependencySet(group: 'io.rest-assured', version: '4.5.1') {
entry 'rest-assured'
entry 'json-path'
entry 'xml-path'
}
}
}
// Configuration for the sonarqube plugin is now in GitHub Actions
// Configuration for the spotless plugin
@@ -415,6 +265,7 @@ configure(project.fineractJavaProjects) {
apply plugin: 'com.github.andygoossens.modernizer'
apply plugin: 'se.thinkcode.cucumber-runner'
/* define the valid syntax level for source files */
sourceCompatibility = JavaVersion.VERSION_17
/* define binary compatibility version */
@@ -528,8 +379,8 @@ configure(project.fineractJavaProjects) {
// Configuration for the Checkstyle plugin
// https://docs.gradle.org/current/userguide/checkstyle_plugin.html
dependencies {
checkstyle 'com.puppycrawl.tools:checkstyle:10.1'
checkstyle 'com.github.sevntu-checkstyle:sevntu-checks:1.41.0'
checkstyle 'com.puppycrawl.tools:checkstyle:10.2'
checkstyle 'com.github.sevntu-checkstyle:sevntu-checks:1.42.0'
}
// Configuration for JaCoCo code coverage task
+51
View File
@@ -0,0 +1,51 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
plugins {
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'groovy'
id 'java-gradle-plugin'
id 'groovy-gradle-plugin'
}
apply from: "${projectDir}/src/main/groovy/org.apache.fineract.dependencies.gradle"
description = 'Fineract Gradle Plugin'
repositories {
gradlePluginPortal()
mavenCentral()
}
dependencies {
implementation 'com.sun.mail:jakarta.mail'
implementation 'org.freemarker:freemarker'
implementation 'org.tmatesoft.svnkit:svnkit'
implementation 'org.bouncycastle:bcprov-jdk15on'
implementation 'org.bouncycastle:bcpg-jdk15on'
implementation 'org.eclipse.jgit:org.eclipse.jgit'
implementation 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache'
implementation 'com.vdurmont:semver4j'
implementation 'org.beryx:text-io'
implementation 'commons-io:commons-io'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor'
implementation 'com.squareup.retrofit2:retrofit'
implementation 'com.squareup.retrofit2:converter-jackson'
implementation 'com.fasterxml.jackson.core:jackson-databind'
}
@@ -0,0 +1,183 @@
/**
* 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: 'io.spring.dependency-management'
// 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.19'
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.7'
mavenBom 'org.jetbrains.kotlin:kotlin-bom:1.6.21'
mavenBom 'org.junit:junit-bom:5.8.2'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.2.1'
mavenBom 'io.cucumber:cucumber-bom:7.3.3'
mavenBom 'io.netty:netty-bom:4.1.76.Final'
mavenBom 'org.mockito:mockito-bom:4.5.1'
mavenBom 'io.github.swagger2markup:swagger2markup-bom:1.3.4'
}
dependencies {
// We use fixed versions, instead of inheriting them from the Spring BOM, to be able to be on more recent ones.
// We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things.
// We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available.
dependency 'org.slf4j:slf4j-api:1.7.36'
dependency 'org.slf4j:slf4j-simple:1.7.36'
dependency 'org.slf4j:jcl-over-slf4j:1.7.36'
dependency 'org.slf4j:jul-to-slf4j:1.7.36'
dependency 'org.slf4j:log4j-over-slf4j:1.7.36'
dependency 'ch.qos.logback:logback-core:1.2.11'
dependency 'ch.qos.logback:logback-classic:1.2.11'
dependency 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10'
dependency 'com.google.guava:guava:31.1-jre'
dependency 'com.google.code.gson:gson:2.9.0'
dependency 'com.google.truth:truth:1.1.3'
dependency 'com.google.truth.extensions:truth-java8-extension:1.1.3'
dependency 'com.google.googlejavaformat:google-java-format:1.15.0'
dependency ('org.apache.commons:commons-email:1.5') {
exclude 'com.sun.mail:javax.mail'
exclude 'javax.activation:activation'
}
dependency 'commons-io:commons-io:2.11.0'
dependency 'com.github.librepdf:openpdf:1.3.27'
dependency ('org.mnode.ical4j:ical4j:3.2.2') {
exclude 'com.sun.mail:javax.mail'
}
dependency 'org.quartz-scheduler:quartz:2.3.2'
dependency 'com.amazonaws:aws-java-sdk-s3:1.12.210'
dependency 'org.ehcache:ehcache:3.10.0'
dependency 'com.github.spullara.mustache.java:compiler:0.9.10'
dependency 'com.jayway.jsonpath:json-path:2.7.0'
dependency 'org.apache.tika:tika-core:2.3.0'
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
dependency 'jakarta.management.j2ee:jakarta.management.j2ee-api:1.1.4'
dependency 'jakarta.jms:jakarta.jms-api:2.0.3'
dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.6' // Swagger needs exactly this version
dependency 'org.apache.bval:org.apache.bval.bundle:2.0.5'
dependency 'joda-time:joda-time:2.10.14'
dependency 'io.github.classgraph:classgraph:4.8.146'
dependency 'org.awaitility:awaitility:4.2.0'
dependency 'com.github.spotbugs:spotbugs-annotations:4.6.0'
dependency 'javax.cache:cache-api:1.1.1'
dependency 'org.mock-server:mockserver-junit-jupiter:5.13.2'
dependency 'org.webjars:webjars-locator-core:0.50'
dependency 'com.icegreen:greenmail-junit5:1.6.8'
dependency 'com.intuit.karate:karate-junit5:1.2.0.RC6'
// fineract client dependencies
dependency "com.squareup.retrofit2:retrofit:2.9.0"
dependency "com.squareup.retrofit2:retrofit-mock:2.9.0"
dependency "com.squareup.retrofit2:adapter-java8:2.9.0"
dependency "com.squareup.retrofit2:adapter-rxjava2:2.9.0"
dependency "com.squareup.retrofit2:adapter-rxjava3:2.9.0"
dependency "com.squareup.retrofit2:adapter-guava:2.9.0"
dependency "com.squareup.retrofit2:converter-wire:2.9.0"
dependency "com.squareup.retrofit2:converter-jackson:2.9.0"
dependency "com.squareup.retrofit2:converter-simplexml:2.9.0"
dependency "com.squareup.retrofit2:converter-jaxb:2.9.0"
dependency "com.squareup.retrofit2:converter-java8:2.9.0"
dependency "com.squareup.retrofit2:converter-scalars:2.9.0"
dependency "com.squareup.retrofit2:converter-gson:2.9.0"
dependency "com.squareup.retrofit2:converter-protobuf:2.9.0"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.common:1.0.1"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.1"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.httpclient4:1.0.1"
dependency "com.squareup.okhttp3:okhttp:4.9.3"
dependency "com.squareup.okhttp3:okcurl:4.9.3"
dependency "com.squareup.okhttp3:logging-interceptor:4.9.3"
dependency "com.squareup.okhttp3:okhttp-apache:4.9.3"
dependency "com.squareup.okhttp3:okhttp-android-support:4.9.3"
dependency "com.squareup.okhttp3:okhttp-urlconnection:4.9.3"
dependency "com.squareup.okhttp3:okhttp-sse:4.9.3"
dependency "io.gsonfire:gson-fire:1.8.5"
dependency "com.google.code.findbugs:jsr305:3.0.2"
dependency "commons-codec:commons-codec:1.15"
dependency "org.projectlombok:lombok:1.18.24"
dependency 'org.bouncycastle:bcpkix-jdk15to18:1.70'
dependency 'org.bouncycastle:bcprov-jdk15to18:1.70'
dependency 'org.bouncycastle:bcprov-jdk15on:1.70'
dependency 'org.bouncycastle:bcpg-jdk15on:1.70'
dependency 'org.eclipse.jgit:org.eclipse.jgit:6.1.0.202203080745-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.1.0.202203080745-r'
dependency 'org.tmatesoft.svnkit:svnkit:1.10.5'
dependency 'com.vdurmont:semver4j:3.1.0'
dependency 'org.beryx:text-io:3.4.1'
dependency 'org.springdoc:springdoc-openapi-ui:1.6.8'
dependency 'org.springdoc:springdoc-openapi-webmvc-core:1.6.8'
dependency 'org.springdoc:springdoc-openapi-common:1.6.8'
dependency 'org.springdoc:springdoc-openapi-security:1.6.8'
dependency 'org.springdoc:springdoc-openapi-javadoc:1.6.8'
dependency 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.5.0'
dependency ('org.apache.activemq:activemq-broker:5.17.1') {
exclude 'javax.annotation:javax.annotation-api'
}
dependency 'io.swagger:swagger-annotations:1.6.6'
dependency 'io.swagger.core.v3:swagger-annotations:2.2.0'
dependency ('io.swagger.core.v3:swagger-jaxrs2:2.2.0') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency ('io.swagger.core.v3:swagger-core:2.2.0') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency "jakarta.annotation:jakarta.annotation-api:1.3.5"
dependency 'jakarta.activation:jakarta.activation-api:1.2.2'
dependency ('com.sun.mail:jakarta.mail:1.6.7') {
exclude 'com.sun.activation:jakarta.activation'
}
dependency ('jakarta.xml.bind:jakarta.xml.bind-api:2.3.3') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency ('org.liquibase:liquibase-core:4.9.1') {
exclude 'javax.xml.bind:jaxb-api'
}
dependency ('org.dom4j:dom4j:2.1.3') {
exclude 'relaxngDatatype:relaxngDatatype' // already in com.sun.xml.bind:jaxb-osgi:2.3.0.1
// FINERACT-940 && FINERACT-966 https://github.com/spotbugs/spotbugs/issues/1128
exclude 'xpp3:xpp3'
exclude 'pull-parser:pull-parser'
}
dependency 'org.owasp.esapi:esapi:2.4.0.0'
dependencySet(group: 'org.apache.poi', version: '5.2.2') {
entry 'poi'
entry 'poi-ooxml'
entry 'poi-ooxml-schemas'
}
dependencySet(group: 'io.rest-assured', version: '4.5.1') {
entry 'rest-assured'
entry 'json-path'
entry 'xml-path'
}
}
}
@@ -0,0 +1,263 @@
/**
* 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: 'org.apache.fineract.gradle'
apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle"
fineract {
config {
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
doc {
url = 'git@github.com:apache/fineract-site.git'
directory = "${System.getProperty("java.io.tmpdir")}/fineract-site"
branch = "asf-site"
}
git {
dir = "${projectDir.absolutePath}/.git"
sections = [
[
section: "user",
name: "name",
value: "${findProperty('fineract.config.name')}",
],
[
section: "user",
name: "email",
value: "${findProperty('fineract.config.email')}",
],
[
section: "user",
name: "signingkey",
value: "${findProperty('fineract.config.gnupg.keyName')}",
],
[
section: "commit",
name: "gpgsign",
value: "true",
],
]
}
template {
templateDir = "${projectDir}/buildSrc/src/main/resources"
}
gpg {
keyName = "${findProperty('fineract.config.gnupg.keyName')}"
publicKeyring = "${findProperty('fineract.config.gnupg.publicKeyring')}"
secretKeyring = "${findProperty('fineract.config.gnupg.secretKeyring')}"
password = "${findProperty('fineract.config.gnupg.password')}"
}
smtp {
host = 'smtp.gmail.com'
username = "${findProperty('fineract.config.smtp.username')}"
password = "${findProperty('fineract.config.smtp.password')}"
tls = true
ssl = true
}
subversion {
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
revision = 'HEAD'
}
jira {
url = 'https://issues.apache.org/jira/rest/api/2/'
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
}
confluence {
url = 'https://cwiki.apache.org/confluence/rest/api/'
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
}
}
steps = [
test: [
order: 0,
description: 'Test',
email: [
from: "${findProperty('fineract.config.email')}",
to: "${findProperty('fineract.config.email')}",
mime: 'text/plain',
subject: "[FINERACT] [TEST] 📣 🏁 🗳️ 🚀 📈 🔀 🌠 Propose new release ${version}",
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl"
]
],
subversion: [
url: 'https://dist.apache.org/repos/dist/dev/fineract',
directory: "${System.getProperty("java.io.tmpdir")}"
],
jira: [
query: "project in (\'FINERACT\') and fixVersion = ${findProperty('fineract.release.version')} and status in (CLOSED, RESOLVED)"
],
template: [
templateFile: "${projectDir}/buildSrc/src/main/resources/jira/changelog.txt.ftl"
],
gpg: [
file: "${projectDir}/buildSrc/build/libs/buildSrc.jar"
]
],
step1: [
order: 1,
description: 'Send heads-up email to dev mailing list',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step01.headsup.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl"
]
]
],
step2: [
order: 2,
description: 'Clean up JIRA',
],
step3: [
order: 3,
description: 'Create a release branch',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step03.branch.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step03.branch.message.ftl"
]
]
],
step4: [
order: 4,
description: 'Freeze JIRA version',
],
step5: [
order: 5,
description: 'Create release tag',
git: [
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/git/release.step05.tag.message.ftl"
]
]
],
step6: [
order: 6,
description: 'Create a distribution',
],
step7: [
order: 7,
description: 'Sign the distribution artifacts',
gpg: [
file: "${rootDir}/fineract-war/build/distributions/apache-fineract-binary-${project.version}.tar.gz"
]
],
step8: [
order: 8,
description: 'Upload distribution artifacts to staging environment',
subversion: [
url: 'https://dist.apache.org/repos/dist/dev/fineract',
directory: "${System.getProperty("java.io.tmpdir")}/fineract-dist-dev"
],
],
step9: [
order: 9,
description: 'Verify distribution in staging environment',
],
step10: [
order: 10,
description: 'Start voting on the dev mailing list',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step10.vote.message.ftl"
]
]
],
step11: [
order: 11,
description: 'Conclude the vote and announce results',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step11.vote.message.ftl"
]
]
],
step12: [
order: 12,
description: 'Upload distribution artifacts to release environment',
subversion: [
url: 'https://dist.apache.org/repos/dist/release/fineract',
directory: "${System.getProperty("java.io.tmpdir")}/fineract-dist-release"
],
],
step13: [
order: 13,
description: 'Close release branch and merge to \'develop\'',
],
step14: [
order: 14,
description: 'Update website (not yet automated)',
],
step15: [
order: 15,
description: 'Send email to announcement mailing list',
jira: [
projectId: "12319420",
query: "project in (\'FINERACT\') and fixVersion = ${findProperty('fineract.release.version')} and status in (CLOSED, RESOLVED, WONTFIX)"
],
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step15.announce.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step15.announce.message.ftl"
]
]
]
]
}
@@ -0,0 +1,581 @@
/**
* 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.gradle
import groovy.json.JsonSlurper
import org.apache.commons.io.FileUtils
import org.apache.commons.io.filefilter.NotFileFilter
import org.apache.commons.io.filefilter.PrefixFileFilter
import org.apache.fineract.gradle.service.*
import org.beryx.textio.TextIO
import org.beryx.textio.TextIoFactory
import org.beryx.textio.TextTerminal
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class FineractPlugin implements Plugin<Project> {
private static final Logger log = LoggerFactory.getLogger(FineractPlugin.class)
private JiraService jiraService
private ConfluenceService confluenceService
private SubversionService subversionService
private EmailService emailService
private GpgService gpgService
private TemplateService templateService
private GitService gitService
private Map<String, ?> context;
static {
System.setProperty("org.beryx.textio.TextTerminal", "org.beryx.textio.system.SystemTextTerminal")
}
void apply(Project project) {
def extension = project.extensions.create("fineract", FineractPluginExtension, project)
project.fineract.extensions.create("config", FineractPluginExtension.FineractPluginConfig)
project.fineract.config.extensions.create("doc", FineractPluginExtension.FineractPluginConfigDoc)
project.fineract.config.extensions.create("template", FineractPluginExtension.FineractPluginConfigTemplate)
project.fineract.config.extensions.create("smtp", FineractPluginExtension.FineractPluginConfigSmtp)
project.fineract.config.extensions.create("subversion", FineractPluginExtension.FineractPluginConfigSubversion)
project.fineract.config.extensions.create("jira", FineractPluginExtension.FineractPluginConfigJira)
project.fineract.config.extensions.create("confluence", FineractPluginExtension.FineractPluginConfigConfluence)
project.fineract.config.extensions.create("gpg", FineractPluginExtension.FineractPluginConfigGpg)
project.fineract.config.extensions.create("git", FineractPluginExtension.FineractPluginConfigGit)
project.afterEvaluate {
this.templateService = new TemplateService(extension.config.template)
this.jiraService = new JiraService(extension.config.jira)
this.confluenceService = new ConfluenceService(extension.config.confluence)
this.subversionService = new SubversionService(extension.config.subversion)
this.emailService = new EmailService(extension.config.smtp)
this.gpgService = new GpgService(extension.config.gpg)
this.gitService = new GitService(extension.config.git, extension.config.gpg)
this.context = context(project)
}
project.tasks.register("fineractDocPublish") {
dependsOn(":fineract-doc:doc")
doLast {
log.warn("Fineract Publish Doc...")
def url = extension.config.doc.url?:"git@github.com:apache/fineract-site.git";
def branch = extension.config.doc.branch?:"asf-site"
def directory = extension.config.doc.directory?:System.getProperty("java.io.tmpdir")+"/fineract-site"
def d = new File(directory)
if(d.exists()) {
gitService.pull(directory)
} else {
gitService.clone(url, branch, directory)
gitService.config(directory, extension.config.git.sections)
}
def source = new File("fineract-doc/build/docs/html/en");
def target = new File(directory + "/docs/current")
if(!target.name) {
target.mkdirs()
}
FileUtils.copyDirectory(source, target, new NotFileFilter(new PrefixFileFilter(".asciidoctor")), true)
gitService.commit(directory)
}
}
project.tasks.register("fineractReleaseTest") {
doFirst {
// emojis:
// https://gist.github.com/parmentf/035de27d6ed1dce0b36a
// https://emojipedia.org/
// https://unicode.org/emoji/charts/full-emoji-list.html
FineractPluginExtension.FineractPluginStep step = step(extension, "test")
// TODO:
// - jira:
// - move tickets to new version
// jira query
FineractPluginExtension.FineractPluginJiraParams result = jiraService.search(step.jira)
log.warn(">>>>>>>>>>>>>>>> ISSUE: ${result.result[0]?.id} - ${result.result[0]?.key} - ${result.result[0]?.fields?['summary']} - ${result.result[0]?.fields?['fixVersions']}")
/*
// confluence create
def content = new ConfluenceService.ConfluenceContent(title: "1.7.0 - Apache Fineract",
space: new ConfluenceService.ConfluenceSpace(key: "FINERACT"),
ancestors: [new ConfluenceService.ConfluenceParent(id: "75974324")],
body: new ConfluenceService.ConfluenceBody(storage: new ConfluenceService.ConfluenceStorage(value: "<h1>Release Artifacts</h1>")))
confluenceService.createContent(content)
// confluence get
ConfluenceService.ConfluenceContent content = confluenceService.getContent("211881930")
log.warn(">>>>>>>>>>>>>>>>>>>>> CONFLUENCE: ${content.id} - ${content.title} - ${content.body?.storage?.value}")
// confluence update
content.body?.storage?.value = "<h1>Release Artifacts</h1><p><ac:structured-macro ac:macro-id=\"ac1cb3cb-84d4-45aa-820b-05cc42d39dd9\" ac:name=\"jira\" ac:schema-version=\"1\"><ac:parameter ac:name=\"server\">ASF JIRA</ac:parameter><ac:parameter ac:name=\"columnIds\">issuekey,summary,issuetype,created,updated,duedate,assignee,reporter,customfield_12311032,customfield_12311037,customfield_12311022,customfield_12311027,priority,status,resolution</ac:parameter><ac:parameter ac:name=\"columns\">key,summary,type,created,updated,due,assignee,reporter,Priority,Priority,Priority,Priority,priority,status,resolution</ac:parameter><ac:parameter ac:name=\"maximumIssues\">50</ac:parameter><ac:parameter ac:name=\"jqlQuery\">project = FINERACT AND fixVersion = 1.7.0 AND status not in (Open)</ac:parameter><ac:parameter ac:name=\"serverId\">5aa69414-a9e9-3523-82ec-879b028fb15b</ac:parameter></ac:structured-macro></p><p><br/></p>"
content.space = new ConfluenceService.ConfluenceSpace(key: "FINERACT")
content.status = null
content.version = new ConfluenceService.ConfluenceVersion(number: 3)
// confluence update
content = confluenceService.updateContent(content.id, content)
log.warn(">>>>>>>>>>>>>>>>>>>>> CONFLUENCE: ${content.id} - ${content.title} - ${content.body?.storage?.value}")
// confluence delete
ConfluenceService.ConfluenceResponse response = confluenceService.deleteContent("211881930")
log.warn(">>>>>>>>>>>>>>>>>>>>> CONFLUENCE: ${response?.statusCode} - ${response?.message}")
// jira versions
def versions = jiraService.getProjectVersions("12319420")
versions.findAll {
log.warn(">>>> VERSION: ${it.id} - ${it.name} - ${it.description}")
}
def version = jiraService.getVersion("12351431")
log.warn(">>>> !!! VERSION: ${version.id} - ${version.name} - ${version.description} - ${version.archived}")
version.archived = !version.archived
jiraService.updateVersion(version)
version = jiraService.getVersion("12351431")
log.warn(">>>> === VERSION: ${version.id} - ${version.name} - ${version.description} - ${version.archived}")
// jira projects
def projects = jiraService.getProjects()
projects.findAll {
log.warn(">>>>>>>>>>>> PROJECT: ${it.id} - ${it.key} - ${it.name}")
}
// jira info
Map<String, Object> info = jiraService.serverInfo()
log.warn(">>>>>>>>>>>>>>>> INFO: ${info}")
// jira query
FineractPluginExtension.FineractPluginJiraParams result = jiraService.search(step.jira)
log.warn(">>>>>>>>>>>>>>>> INFO: ${result.result.size()}")
// format with template
def tmp = templateService.render(step.template, [issues: result.result])
log.warn(tmp.output)
*/
}
}
// TODO: step for creating release Jira ticket missing!
// step 1
project.tasks.register("fineractReleaseStep1") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step1")
String version = project.properties?['fineract.release.version']
String issue = project.properties?['fineract.release.issue']
String date = project.properties?['fineract.release.date']
if(!version || !issue || !date) {
TextIO textIO = TextIoFactory.getTextIO()
if(!version) {
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
if(!issue) {
issue = textIO.newStringInputReader()
.withMaxLength(4)
.withPattern("\\d+")
.read("Jira Issue");
}
if(!date) {
date = textIO.newStringInputReader()
.withMaxLength(10)
.withPattern("\\d\\d\\d\\d-\\d\\d-\\d\\d")
.read("Date");
}
TextTerminal terminal = textIO.getTextTerminal()
terminal.printf("\nResult: %s - FINERACT-%s - %s", version, issue, date)
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.issue'] = issue
this.context?.project?['fineract.release.date'] = date
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 2
project.tasks.register("fineractReleaseStep2") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step2")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step2")
}
}
// step 3
project.tasks.register("fineractReleaseStep3") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step3")
String version = project.properties?['fineract.release.version']
String date = project.properties?['fineract.release.date']
if(!version || !date) {
TextIO textIO = TextIoFactory.getTextIO()
if(!version) {
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
if(!date) {
date = textIO.newStringInputReader()
.withMaxLength(10)
.withPattern("\\d\\d\\d\\d-\\d\\d-\\d\\d")
.read("Date");
}
TextTerminal terminal = textIO.getTextTerminal()
terminal.printf("\nResult: %s - %s", version, date)
}
// TODO: input validation, see FINERACT-1610
// TODO: create release branch, see FINERACT-1611
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.date'] = date
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 4
project.tasks.register("fineractReleaseStep4") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step4")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step4")
}
}
// step 5
project.tasks.register("fineractReleaseStep5") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step5")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
if (!version) {
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
}
this.context?.project?['fineract.release.version'] = version
def params = processGitParams(step.git, this.context)
params.tag = params.tag?:version
gitService.createTag(params.tag, params.message)
}
}
// step 6
project.tasks.register("fineractReleaseStep6") {
dependsOn(":fineract-war:binaryDistTar")
}
// step 7
project.tasks.register("fineractReleaseStep7") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step7")
gpgService.sign(step.gpg)
def md5 = gpgService.md5(step.gpg)
def md5File = new File("${step.gpg.file}.md5")
md5File.write md5
def sha512 = gpgService.sha512(step.gpg)
def sha512File = new File("${step.gpg.file}.sha512")
sha512File.write sha512
}
}
// step 8
project.tasks.register("fineractReleaseStep8") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step8")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
subversionService.checkout(step.subversion)
def directory = step.subversion.directory?:System.getProperty("java.io.tmpdir") + "/fineract-dist-dev"
def source = new File("fineract-war/build/distributions")
def target = new File("${directory}/${version}")
FileUtils.copyDirectory(source, target, true)
subversionService.commit(step.subversion)
}
}
// step 9
project.tasks.register("fineractReleaseStep9") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step9")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step9")
}
}
// step 10
project.tasks.register("fineractReleaseStep10") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step10")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO();
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 11
project.tasks.register("fineractReleaseStep11") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step11")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
def jsonSlurper = new JsonSlurper()
def data = jsonSlurper.parse(new File("buildSrc/src/main/resources/vote/result.${version}.json"))
this.context?.project?['fineract.vote'] = data
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 12
project.tasks.register("fineractReleaseStep12") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step12")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
subversionService.checkout(step.subversion)
def directory = step.subversion.directory?:System.getProperty("java.io.tmpdir") + "/fineract-dist-release"
def source = new File("fineract-war/build/distributions")
def target = new File("${directory}/${version}")
FileUtils.copyDirectory(source, target, true)
subversionService.commit(step.subversion)
}
}
// step 13
project.tasks.register("fineractReleaseStep13") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step13")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step13")
}
}
// step 14
project.tasks.register("fineractReleaseStep14") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step14")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step14")
}
}
// step 15
project.tasks.register("fineractReleaseStep15") {
doFirst {
log.warn("Release step 15: send email to announcement mailing list")
FineractPluginExtension.FineractPluginStep step = step(extension, "step15")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
FineractPluginExtension.FineractPluginJiraParams issues = jiraService.search(step.jira)
def versions = jiraService.getProjectVersions(step.jira.projectId)
def filteredVersions = versions.findAll {
log.warn(">>>> VERSION: ${it.id} - ${it.name} - ${it.description}")
it.name == version
}
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.issues'] = issues.result
this.context?.project?['fineract.release.projectId'] = step.jira.projectId
this.context?.project?['fineract.release.versionId'] = filteredVersions[0]?.id
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
}
private FineractPluginExtension.FineractPluginStep step(FineractPluginExtension extension, String id) {
FineractPluginExtension.FineractPluginStep step = extension.steps[id]
if(step) {
log.warn("Fineract release step ${step.order}: ${step.description}.")
} else {
throw new RuntimeException("Could not find any parameters for step with ID '${id}'")
}
return step
}
private FineractPluginExtension.FineractPluginEmailParams processEmailParams(FineractPluginExtension.FineractPluginEmailParams params, Object data) {
if(params.subjectTemplate) {
def result = templateService.render(params.subjectTemplate, data)
params.subject = result.output
}
if(params.messageTemplate) {
def result = templateService.render(params.messageTemplate, data)
params.message = result.output
}
return params
}
private FineractPluginExtension.FineractPluginGitParams processGitParams(FineractPluginExtension.FineractPluginGitParams params, Object data) {
if(params.messageTemplate) {
def result = templateService.render(params.messageTemplate, data)
params.message = result.output
}
return params
}
private Map<String, ?> context(Project project) {
return Map.of("project", project.getProperties().findAll { it.key != "password"})
}
private void printInstructions(Project project, String step) {
String version = project.properties?['fineract.release.version']?:"0.0.0"
this.context?.project?['fineract.release.version'] = version
FineractPluginExtension.FineractPluginTemplateParams result = templateService.render(new FineractPluginExtension.FineractPluginTemplateParams(templateFile: new File("buildSrc/src/main/resources/instructions/${step}.txt.ftl")), this.context)
log.warn(result.output)
}
}
@@ -0,0 +1,164 @@
/**
* 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.gradle
import org.apache.fineract.gradle.service.JiraService
import org.gradle.api.Project
class FineractPluginExtension {
Project project
Map<String, FineractPluginStep> steps = [:]
FineractPluginExtension(Project project) {
this.project = project
}
static class FineractPluginConfig {
String username
String password
}
static class FineractPluginConfigDoc {
String url
String directory
String branch
}
static class FineractPluginConfigJira {
String url
String username
String password
String project
}
static class FineractPluginConfigConfluence {
String url
String username
String password
String project
}
static class FineractPluginConfigGpg {
String keyName
String publicKeyring
String secretKeyring
String password
}
static class FineractPluginConfigTemplate {
String templateDir
}
static class FineractPluginConfigSmtp {
String host
int port
String username
String password
boolean tls
boolean ssl
}
static class FineractPluginConfigGit {
String username
String password
String dir
boolean dryRun
List<FineractPluginConfigGitSection> sections
}
static class FineractPluginConfigGitSection {
String section
String subsection
String name
String value
}
static class FineractPluginConfigSubversion {
String username
String password
String revision
}
static class FineractPluginStep {
int order
String description
FineractPluginEmailParams email
FineractPluginJiraParams jira
FineractPluginTemplateParams template
FineractPluginConfluenceParams confluence
FineractPluginGitParams git
FineractPluginSubversionParams subversion
FineractPluginGpgParams gpg
}
static class FineractPluginEmailParams {
String from
String name
String to
String cc
String bcc
String mime
String subject
FineractPluginTemplateParams subjectTemplate
String message
FineractPluginTemplateParams messageTemplate
}
static class FineractPluginJiraParams {
String command
String projectId
String fields = "*all"
String query
List<JiraService.JiraIssue> result = new ArrayList<>()
int pageOffset = 0
int pageSize= 50
int total = 1000
List<String> includes = ["summary", "status", "assignee", "fixVersions"]
}
static class FineractPluginConfluenceParams {
String title
String content
Integer ancestor
}
static class FineractPluginGitParams {
String tag
String message
FineractPluginTemplateParams messageTemplate
}
static class FineractPluginSubversionParams {
String url
String command
String revision = "HEAD"
String directory
}
static class FineractPluginTemplateParams {
String template
String templateFile
String output
String outputFile
}
static class FineractPluginGpgParams {
String file
}
}
@@ -0,0 +1,209 @@
/**
* 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.gradle.service
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import okhttp3.ConnectionPool
import okhttp3.Credentials
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.apache.fineract.gradle.FineractPluginExtension.FineractPluginConfigConfluence
import org.jetbrains.annotations.NotNull
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.*
import java.util.concurrent.TimeUnit
class ConfluenceService {
private static final Logger log = LoggerFactory.getLogger(ConfluenceService.class)
private ConfluenceApi api
ConfluenceService(FineractPluginConfigConfluence config) {
def credentials = Credentials.basic(config.username, config.password)
final ConnectionPool connectionPool = new ConnectionPool(5, 60, TimeUnit.SECONDS);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(log::debug)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder okClient = new OkHttpClient.Builder()
.connectionPool(connectionPool)
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
@Override
Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
def request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
})
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(config.url)
.client(okClient.build())
.addConverterFactory(JacksonConverterFactory.create(mapper))
Retrofit retrofit = builder.build()
this.api = retrofit.create(ConfluenceApi.class)
}
ConfluenceContent getContent(String id) {
this.api.getContentById(id).execute().body()
}
ConfluenceContent createContent(ConfluenceContent content) {
this.api.postContent(content).execute().body()
}
ConfluenceContent updateContent(String id, ConfluenceContent content) {
this.api.putContent(id, content).execute().body()
}
ConfluenceResponse deleteContent(String id) {
this.api.deleteContentById(id).execute().body()
}
static class ConfluenceContent {
String id
String type
String title
String status
String self
List<ConfluenceParent> ancestors
ConfluenceSpace space
ConfluenceBody body
ConfluenceVersion version
ConfluenceHistory history
}
static class ConfluenceContentResult {
List<ConfluenceContent> results
private int start
private int limit
private int size
}
static class ConfluenceParent {
String id
String type
}
static class ConfluenceVersion {
ConfluenceCreatedBy by
Date when
String message
Integer number
}
static class ConfluenceCreatedBy {
String username
String displayName
}
static class ConfluenceHistory {
boolean latest
ConfluenceCreatedBy createdBy
ConfluenceVersion nextVersion
ConfluenceVersion previousVersion
ConfluenceVersion lastUpdated
}
static class ConfluenceSpace {
String key
String name
}
static class ConfluenceSpaceResult {
List<ConfluenceSpace> results
}
static class ConfluenceBody {
ConfluenceStorage storage
}
static class ConfluenceStorage {
String value
String representation
}
static class ConfluenceResponse {
int statusCode
String message
Map<String, ?> data
}
static interface ConfluenceApi {
@GET("content")
Call<ConfluenceContentResult> getContentResults();
@GET("content")
Call<ConfluenceContentResult> getContentBySpaceKeyAndTitle(final @Query("key") String key, final @Query("title") String title);
@GET("content/{id}/child/{type}")
Call<ConfluenceContentResult> getChildren(final @Path("id") String parentId, final @Path("type") String type, final @QueryMap Map<String, String> params);
@GET("content/{id}?expand=body.storage")
Call<ConfluenceContent> getContentById(final @Path("id") String id);
@POST("contentbody/convert/{to}")
Call<ConfluenceStorage> postContentConversion(final @Body ConfluenceStorage storage, final @Path("to") String convertToFormat);
@POST("content")
Call<ConfluenceContent> postContent(final @Body ConfluenceContent content);
@PUT("content/{id}")
Call<ConfluenceContent> putContent(final @Path("id") String id, final @Body ConfluenceContent content);
@POST("content")
void postContentWithCallback(final @Body ConfluenceContent content, final Callback<ConfluenceContent> callback);
@DELETE("content/{id}")
Call<ConfluenceResponse> deleteContentById(final @Path("id") String id);
@GET("space")
Call<ConfluenceSpaceResult> getSpaces();
@POST("space")
Call<ConfluenceSpace> createSpace(final @Body ConfluenceSpace space);
@POST("space/_private")
Call<ConfluenceSpace> createPrivateSpace(final @Body ConfluenceSpace space);
@GET("space/{spaceKey}/content/page")
Call<ConfluenceContentResult> getAllSpaceContent(final @Path("spaceKey") String spaceKey, final @QueryMap Map<String, String> params);
@GET("space/{spaceKey}/content/{type}")
Call<ConfluenceContentResult> getRootContentBySpaceKey(final @Path("spaceKey") String spaceKey, final @Path("type") String contentType);
}
}
// curl -u aleks:xxx -X POST -H 'Content-Type: application/json' -d '{"type":"page","title":"1.7.0 - Apache Fineract","space":{"key":"FINERACT"},"ancestors":[{"id":75974324}],"body":{"storage":{"value":"<p>This is <br/> a new page</p>","representation": "storage"}}}' https://cwiki.apache.org/confluence/content/ | python -mjson.tool
@@ -0,0 +1,87 @@
/**
* 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.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.apache.fineract.gradle.FineractPluginExtension.FineractPluginEmailParams
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.mail.*
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage
class EmailService {
private static final Logger log = LoggerFactory.getLogger(EmailService.class)
String username
String password
Properties properties
EmailService(FineractPluginExtension.FineractPluginConfigSmtp config) {
this.username = config.username
this.password = config.password
this.properties = new Properties()
this.properties.put("mail.smtp.host", config.host)
this.properties.put("mail.smtp.auth", "true")
this.properties.put("mail.smtp.starttls.enable", config.tls.toString())
if(config.ssl) {
this.properties.put("mail.smtp.port", "465")
this.properties.put("mail.smtp.socketFactory.port", "465");
this.properties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
} else {
this.properties.put("mail.smtp.port", "587")
}
}
void send(FineractPluginEmailParams params) {
log.warn("Sending email: ${params.from} -> ${params.to} - ${params.subject}")
Session session = Session.getInstance(this.properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(getUsername(), getPassword())
}
})
try {
Message msg = new MimeMessage(session)
msg.setFrom(new InternetAddress(params.from, params.name))
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(params.to))
if(params.cc) {
msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(params.cc))
}
if(params.bcc) {
msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(params.bcc))
}
msg.setSubject(params.subject)
msg.setText(params.message);
Transport.send(msg);
log.info("Done")
} catch (MessagingException e) {
log.error(e.toString(), e)
}
}
}
@@ -0,0 +1,136 @@
/**
* 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.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.eclipse.jgit.annotations.NonNull
import org.eclipse.jgit.annotations.Nullable
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.errors.CanceledException
import org.eclipse.jgit.lib.CommitBuilder
import org.eclipse.jgit.lib.GpgSigner
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.lib.StoredConfig
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.RefSpec
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class GitService {
private static final Logger log = LoggerFactory.getLogger(GitService.class)
private Git git
private boolean dryRun
GitService(FineractPluginExtension.FineractPluginConfigGit config, FineractPluginExtension.FineractPluginConfigGpg gpgConfig) {
GpgSigner.setDefault(new GpgSigner() {
@Override
void sign(@NonNull CommitBuilder commit, @Nullable String gpgSigningKey, @NonNull PersonIdent committer, CredentialsProvider credentialsProvider) throws CanceledException {
log.warn("------------------------ KEY: ${gpgSigningKey} IDENT: ${committer}")
}
@Override
boolean canLocateSigningKey(@Nullable String gpgSigningKey, @NonNull PersonIdent committer, CredentialsProvider credentialsProvider) throws CanceledException {
log.warn("------------------------ KEY: ${gpgSigningKey} IDENT: ${committer}")
return false
}
});
Repository repository = new FileRepositoryBuilder()
.readEnvironment()
.setGitDir(new File(config.dir))
.findGitDir()
.build();
this.git = new Git(repository)
this.dryRun = config.dryRun
}
void clone(String url, String branch, String directory) {
def ref = "refs/heads/" + branch
Git.cloneRepository()
.setURI(url)
.setDirectory(new File(directory))
.setBranchesToClone(Arrays.asList(ref))
.setBranch(ref)
.call();
}
void pull(String directory) {
Git git = Git.open(new File(directory));
git.pull().call()
}
void commit(String directory) {
Git git = Git.open(new File(directory));
def status = git.status().call()
if(!status.isClean()) {
git.add().addFilepattern(".").call()
git.commit().setSign(true).setMessage("chore: Publish current docs").call()
}
def result = git.push().call()
}
void config(String directory, List<FineractPluginExtension.FineractPluginConfigGitSection> sections) {
Git git = Git.open(new File(directory));
StoredConfig gitConfig = git.getRepository().getConfig();
sections.forEach(section -> {
gitConfig.setString(section.section, section.subsection, section.name, section.value);
})
// gitConfig.setString("branch", "master", "merge", "refs/heads/master");
gitConfig.save();
}
void createTag(String name, String message) {
def ref = git.tag()
.setName(name)
.setMessage(message)
.setAnnotated(true)
.setForceUpdate(true)
.call()
try {
git.push().setPushTags().setForce(true).setDryRun(dryRun).call()
log.warn("Tag created: ${name} (${ref.name})")
} catch(Exception e) {
log.error(e.toString(), e)
}
}
// TODO: explicitly provide the directory
void removeTag(String name) {
git.tagDelete().setTags(name).call()
RefSpec refSpec = new RefSpec(":refs/tags/${name}");
git.push().setRefSpecs(refSpec).setRemote("origin").call()
log.warn("Tag deleted: ${name}")
}
}
@@ -0,0 +1,148 @@
/**
* 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.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.apache.fineract.gradle.FineractPluginExtension.FineractPluginConfigGpg
import org.bouncycastle.bcpg.ArmoredOutputStream
import org.bouncycastle.bcpg.BCPGOutputStream
import org.bouncycastle.gpg.keybox.BlobType
import org.bouncycastle.gpg.keybox.PublicKeyRingBlob
import org.bouncycastle.gpg.keybox.bc.BcKeyBox
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.security.Security
class GpgService {
private static final Logger log = LoggerFactory.getLogger(GpgService.class)
private PGPPrivateKey privateKey;
private PGPPublicKey publicKey;
static {
Security.addProvider(new BouncyCastleProvider());
}
GpgService(FineractPluginConfigGpg config) {
// TODO: provide implementation for *.gpg key rings
def keyBox = config.publicKeyring.endsWith(".kbx") ? new BcKeyBox(new FileInputStream(config.publicKeyring)) : null
if(keyBox) {
for(def keyBlob : keyBox.getKeyBlobs()) {
switch (keyBlob.type) {
case BlobType.X509_BLOB:
break
case BlobType.OPEN_PGP_BLOB:
def publicKeyRing = (keyBlob as PublicKeyRingBlob).getPGPPublicKeyRing();
def iterator = publicKeyRing.getPublicKeys();
while (publicKey == null && iterator.hasNext()) {
def k = iterator.next();
if (k.isEncryptionKey()) {
def keyName = Long.toHexString(k.keyID).toUpperCase()
if(config.keyName.substring(config.keyName.length()-keyName.length()) == keyName) {
publicKey = k;
log.warn("Found: ${publicKey.getUserIDs().next()}")
break
} else {
log.warn("Key mismatch: ${config.keyName} <-> ${keyName}")
}
}
}
break
}
}
PGPSecretKeyRingCollection secretRingCollection = new PGPSecretKeyRingCollection(new FileInputStream(config.secretKeyring), new BcKeyFingerprintCalculator())
def secretKey = secretRingCollection.getSecretKey(publicKey.keyID)
if(secretKey!=null) {
this.privateKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(config.password.toCharArray()))
}
} else {
log.warn("Could not open the public key ring.")
}
}
void sign(FineractPluginExtension.FineractPluginGpgParams params) {
InputStream is = new FileInputStream(params.file)
BCPGOutputStream os = new BCPGOutputStream(new ArmoredOutputStream(new FileOutputStream(params.file + ".asc")))
sign(is).encode(os)
os.close();
}
PGPSignature sign(InputStream is) throws IOException, PGPException, GeneralSecurityException {
PGPSignatureGenerator generator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(publicKey.getAlgorithm(), PGPUtil.SHA1));
generator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) >= 0) {
generator.update(buf, 0, len);
}
return generator.generate();
}
String md5(FineractPluginExtension.FineractPluginGpgParams params) {
return calc(new FileInputStream(params.file), MessageDigest.getInstance("MD5", BouncyCastleProvider.PROVIDER_NAME))
}
String sha512(FineractPluginExtension.FineractPluginGpgParams params) {
return calc(new FileInputStream(params.file), MessageDigest.getInstance("SHA-512", BouncyCastleProvider.PROVIDER_NAME))
}
private static String calc(InputStream is, MessageDigest digest) {
String output
int read
byte[] buffer = new byte[8192]
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read)
}
byte[] hash = digest.digest()
BigInteger bigInt = new BigInteger(1, hash)
output = bigInt.toString(16)
while ( output.length() < 32 ) {
output = "0"+output
}
} catch (Exception e) {
log.error(e.toString(), e)
return null
}
return output
}
}
@@ -0,0 +1,324 @@
/**
* 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.gradle.service
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import org.apache.fineract.gradle.FineractPluginExtension
import org.jetbrains.annotations.NotNull
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.*
import java.util.concurrent.TimeUnit
class JiraService {
private static final Logger log = LoggerFactory.getLogger(JiraService.class)
private JiraApi api
JiraService(FineractPluginExtension.FineractPluginConfigJira config) {
def credentials = Credentials.basic(config.username, config.password)
final ConnectionPool connectionPool = new ConnectionPool(5, 60, TimeUnit.SECONDS);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(log::debug)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder okClient = new OkHttpClient.Builder()
.connectionPool(connectionPool)
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
@Override
Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
def request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
})
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(config.url)
.client(okClient.build())
.addConverterFactory(JacksonConverterFactory.create(mapper))
Retrofit retrofit = builder.build()
this.api = retrofit.create(JiraApi.class)
}
Map<String, Object> serverInfo() {
return this.api.serverInfo.execute().body()
}
FineractPluginExtension.FineractPluginJiraParams search(FineractPluginExtension.FineractPluginJiraParams params) {
def query = [:]
query['jql'] = params.query
query['startAt'] = params.pageOffset?:0
query['maxResults'] = params.pageSize?:50
// query['expand'] = [
// "names",
// "schema",
// "operations"
// ]
// query['fields'] = params.fields?:"*all"
query['fields'] = params.includes?:[
"summary",
"status",
"assignee",
"fixVersions"
]
// params.total = params.total?:1000
params.total = 1000
def result = this.api.searchIssues(query).execute().body()
params.result.addAll(result?.issues as List)
if(result?.issues.size()>=params.pageSize && params.result.size() < params.total) {
params.pageOffset += params.pageSize
search(params);
}
return params
}
List getProjects() {
return this.api.getProjects()?.execute()?.body()
}
List getProjectVersions(String projectId) {
return this.api.getProjectVersions(projectId)?.execute()?.body()
}
JiraVersion getVersion(String id) {
return this.api.getVersion(id)?.execute()?.body()
}
void updateVersion(JiraVersion version) {
this.api.updateVersion(version.id, version).execute()
}
static class JiraVersion {
String self
String id
String description
String name
boolean archived
boolean released
String releaseDate
String userReleaseDate
String project
Integer projectId
}
static class JiraSearchResult {
String expand
Integer startAt
Integer maxResults
Integer total
List<JiraIssue> issues
List<String> warningMessages
Map<String, Object> names
Map<String, Object> schema
}
static class JiraIssue {
String expand
String self
String id
String key
Map<String, Object> renderedFields
Map<String, Object> properties
Map<String, Object> names
Map<String, Object> schema
Map<String, Object> fields
}
static class JiraIssueUpdateRequest {
Map<String, Map<String, Object>> historyMetadata
Map<String, List<Map<String, Object>>> update
Map<String, Object> fields
Map<String, List<Map<String, Object>>> properties
}
static interface JiraApi {
// Server Info
@GET("serverInfo")
Call<Map<String, Object>> getServerInfo()
// Components
@GET("component/{id}")
Call<Object> getComponent(@Path("id") String id)
@POST("component")
Call<Object> createComponent(@Body Object component)
@PUT("component/{id}")
Call<Void> updateComponent(@Path("id") String id, @Body Object component)
@GET("component/{id}/relatedIssueCounts")
Call<Object> getComponentIssueCount(@Path("id") String id)
// Issue
@GET("issue/{issueIdOrKey}")
Call<Object> getIssue(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue")
Call<Object> createIssue(@Body Object issue)
@PUT("issue/{issueIdOrKey}")
Call<Object> updateIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object issue, @QueryMap Map<String, String> queryMap)
@POST("issue/bulk")
Call<Object> createIssues(@Body Object issues)
@PUT("issue/{issueIdOrKey}/assignee")
Call<Void> assignIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object user)
@GET("issue/{issueIdOrKey}/comment")
Call<Object> getComments(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue/{issueIdOrKey}/comment")
Call<Object> addComment(@Path("issueIdOrKey") String issueIdOrKey, @Body Object comment)
@PUT("issue/{issueIdOrKey}/comment/{id}")
Call<Object> updateComment(@Path("issueIdOrKey") String issueIdOrKey, @Path("id") String id, @Body Object comment)
@GET("issue/{issueIdOrKey}/comment/{id}")
Call<Object> getComment(@Path("issueIdOrKey") String issueIdOrKey, @Path("id") String id)
@POST("issue/{issueIdOrKey}/notify")
Call<Void> notifyIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object notify)
@GET("issue/{issueIdOrKey}/transitions?expand=transitions.fields")
Call<Object> getTransitions(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue/{issueIdOrKey}/transitions")
Call<Void> transitionIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object issue)
@GET("issue/{issueIdOrKey}/watchers")
Call<Object> getIssueWatches(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue/{issueIdOrKey}/watchers")
Call<Void> addIssueWatcher(@Path("issueIdOrKey") String issueIdOrKey, @Body String user)
// Remote Issue Links
@GET("issue/{issueIdOrKey}/remotelink")
Call<Object> getIssueRemoteLinks(@Path("issueIdOrKey") String issueIdOrKey, @Query("globalId") String globalId)
@GET("issue/{issueIdOrKey}/remotelink/{linkId}")
Call<Object> getIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Path("linkId") String linkId)
@POST("issue/{issueIdOrKey}/remotelink")
Call<Object> createIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Body Object issueLink)
@POST("issue/{issueIdOrKey}/remotelink/{linkId}")
Call<Object> updateIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Path("linkId") String linkId, @Body Object issueLink)
@DELETE("issue/{issueIdOrKey}/remotelink")
Call<Object> deleteIssueRemoteLinks(@Path("issueIdOrKey") String issueIdOrKey, @Query("globalId") String globalId)
@DELETE("issue/{issueIdOrKey}/remotelink/{linkId}")
Call<Object> deleteIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Path("linkId") String linkId)
// Issue Links
@POST("issueLink")
Call<Void> createIssueLink(@Body Object issueLink)
@GET("issueLink/{linkId}")
Call<Object> getIssueLink(@Path("linkId") String linkId)
@DELETE("issueLink/{linkId}")
Call<Object> deleteIssueLink(@Path("linkId") String linkId)
// Issue Link Types
@GET("issueLinkType")
Call<Object> getIssueLinkTypes()
// Project
@GET("project?expand=lead,description")
Call<List> getProjects()
@GET("project/{projectIdOrKey}")
Call<Object> getProject(@Path("projectIdOrKey") String projectId)
@GET("project/{projectIdOrKey}/statuses")
Call<Object> getProjectStatuses(@Path("projectIdOrKey") String projectId)
@GET("project/{projectIdOrKey}/components")
Call<Object> getProjectComponents(@Path("projectIdOrKey") String projectId)
@GET("project/{projectIdOrKey}/versions")
Call<List<JiraVersion>> getProjectVersions(@Path("projectIdOrKey") String projectId)
// Search
@POST("search")
Call<JiraSearchResult> searchIssues(@Body Map<String, Object> search)
// Version
@POST("version")
Call<JiraVersion> createVersion(@Body JiraVersion version)
@GET("version/{id}")
Call<JiraVersion> getVersion(@Path("id") String id)
@PUT("version/{id}")
Call<Void> updateVersion(@Path("id") String id, @Body JiraVersion version)
// Fields
@GET("field")
Call<Object> getFields()
@POST("field")
Call<Object> createField(@Body Object field)
// Users
@GET("user/search")
Call<Object> userSearch(@Query("username") String userName, @Query("startAt") int startAt, @Query("maxResults") int maxResults)
@GET("user/assignable/search")
Call<Object> assignableUserSearch(@Query("username") String userName, @Query("project") String project, @Query("issueKey") String issueKey, @Query("startAt") int startAt, @Query("maxResults") int maxResults)
@Multipart
@retrofit2.http.Headers("X-Atlassian-Token: no-check")
@POST("issue/{issueIdOrKey}/attachments")
Call<Object> uploadAttachment(@Path("issueIdOrKey") String issueIdOrKey, @Part MultipartBody.Part file)
@GET("attachment/{attachmentId}")
Call<Object> getAttachment(@Path("attachmentId") String attachmentId)
@DELETE("attachment/{attachmentId}")
Call<Object> deleteAttachment(@Path("attachmentId") String attachmentId)
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrl(@Url String fileUrl)
}
}
@@ -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.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.gradle.api.GradleException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.tmatesoft.svn.core.SVNCancelException
import org.tmatesoft.svn.core.SVNDepth
import org.tmatesoft.svn.core.SVNException
import org.tmatesoft.svn.core.SVNURL
import org.tmatesoft.svn.core.wc.*
class SubversionService {
private static final Logger log = LoggerFactory.getLogger(SubversionService.class)
private SVNUpdateClient client;
private String revision;
SubversionService(FineractPluginExtension.FineractPluginConfigSubversion config) {
ISVNOptions options = SVNWCUtil.createDefaultOptions(true)
SVNClientManager clientManager = SVNClientManager.newInstance(options, SVNWCUtil.createDefaultAuthenticationManager(config.username, config.password))
this.client = clientManager.updateClient;
this.client.ignoreExternals = false
this.client.eventHandler = new ISVNEventHandler() {
@Override
void handleEvent(SVNEvent event, double progress) throws SVNException {
println ">>> PROGRESS: ${progress>0?:'-'}"
}
@Override
void checkCancelled() throws SVNCancelException {
}
}
this.revision = config.revision
}
void checkout(FineractPluginExtension.FineractPluginSubversionParams params) {
SVNURL svnUrl = SVNURL.parseURIEncoded(params.url)
SVNRevision svnRevision = this.revision == null ? SVNRevision.HEAD : SVNRevision.parse(this.revision)
if (svnRevision == SVNRevision.UNDEFINED) {
throw new GradleException("Invalid SVN revision: " + this.revision)
}
this.client.doCheckout(svnUrl, new File(params.directory), SVNRevision.HEAD, svnRevision, SVNDepth.INFINITY, false)
}
void commit(FineractPluginExtension.FineractPluginSubversionParams params) {
// TODO: implement this
SVNURL svnUrl = SVNURL.parseURIEncoded(params.url)
}
}
@@ -0,0 +1,84 @@
/**
* 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.gradle.service
import freemarker.core.Environment
import freemarker.template.Configuration
import freemarker.template.Template
import freemarker.template.TemplateException
import freemarker.template.TemplateExceptionHandler
import org.apache.fineract.gradle.FineractPluginExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class TemplateService {
private static final Logger log = LoggerFactory.getLogger(TemplateService.class)
private Configuration config
TemplateService(FineractPluginExtension.FineractPluginConfigTemplate config) {
def dir = new File(config.templateDir);
this.config = new Configuration(Configuration.VERSION_2_3_31);
this.config.setDirectoryForTemplateLoading(dir)
this.config.setDefaultEncoding("UTF-8");
this.config.setLogTemplateExceptions(false);
this.config.setWrapUncheckedExceptions(true);
this.config.setFallbackOnNullLoopVariable(false);
this.config.setTemplateExceptionHandler(new TemplateExceptionHandler() {
@Override
void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException {
if (!env.isInAttemptBlock()) {
PrintWriter pw = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out);
pw.print("FreeMarker template error\n");
te.printStackTrace(pw, false, true, false);
pw.flush(); // To commit the HTTP response
}
}}
)
}
FineractPluginExtension.FineractPluginTemplateParams render(FineractPluginExtension.FineractPluginTemplateParams params, Object data) {
Template template = null;
if(params.templateFile) {
template = new Template("template", new FileReader(new File(params.templateFile)), this.config)
}
if(params.template) {
template = new Template("template", new StringReader(params.template), this.config)
}
if(template) {
if(params.outputFile) {
def output = new File(params.outputFile)
output.createNewFile()
template.process(data, new FileWriter(output, false))
} else {
def output = new StringWriter()
template.process(data, output)
params.output = output.toString()
}
}
return params
}
}
@@ -0,0 +1,20 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
implementation-class=org.apache.fineract.gradle.FineractPlugin
@@ -0,0 +1,34 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<h1>Release Artifacts</h1>
<p>
<ac:structured-macro ac:macro-id="ac1cb3cb-84d4-45aa-820b-05cc42d39dd9" ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">ASF JIRA</ac:parameter>
<ac:parameter ac:name="columnIds">issuekey,summary,issuetype,created,updated,duedate,assignee,reporter,customfield_12311032,customfield_12311037,customfield_12311022,customfield_12311027,priority,status,resolution</ac:parameter>
<ac:parameter ac:name="columns">key,summary,type,created,updated,due,assignee,reporter,Priority,Priority,Priority,Priority,priority,status,resolution</ac:parameter>
<ac:parameter ac:name="maximumIssues">50</ac:parameter>
<ac:parameter ac:name="jqlQuery">project = FINERACT AND fixVersion = ${project['fineract.release.version']} AND status not in (Open) </ac:parameter>
<ac:parameter ac:name="serverId">5aa69414-a9e9-3523-82ec-879b028fb15b</ac:parameter>
</ac:structured-macro>
</p>
<p>
<br/>
</p>
@@ -0,0 +1,39 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
Hello everyone,
... based on our "How to Release Apache Fineract" process documented at https://cwiki.apache.org/confluence/x/DRwIB:
I will create a ${project['fineract.release.version']} branch off develop in our git repository at https://github.com/apache/fineract on ${project['fineract.release.date']}.
The release tracking umbrella issue for tracking all activity in JIRA is FINERACT-${project['fineract.release.issue']!'0000'} (https://issues.apache.org/jira/browse/FINERACT-${project['fineract.release.issue']!'0000'}) for this Fineract ${project['fineract.release.version']}.
If you have any work in progress that you would like to see included in this release, please add "blocking" links to the release JIRA issue.
I am the release manager for this release.
Cheers,
${project['fineract.config.name']}
🎉 Powered by Fineract Release Plugin 🎊
@@ -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.
-->
[FINERACT] [PROPOSAL] 📦 New release ${project['fineract.release.version']}
@@ -0,0 +1,35 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
Hello everyone,
... as previously announced, I've just created the release branch for our upcoming ${project['fineract.release.version']} release.
You can continue working and merging PRs to the develop branch for future releases, as always.
The DRAFT release notes are on https://cwiki.apache.org/confluence/display/FINERACT/${project['fineract.release.version']}+-+Apache+Fineract. Does anyone see anything missing?
Does anyone have any last minutes changes they would like to see cherry-picked to branch ${project['fineract.release.version']}, or are we good go and actually cut the release based on this branch as it is?
I'll initiate the final stage of actually creating the release on ${project['fineract.release.date']} if nobody objects.
Cheers,
${project['fineract.config.name']}
@@ -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.
-->
[FINERACT] [ANNOUNCE] 🔀 ${project['fineract.release.version']} release branch
@@ -0,0 +1,43 @@
<#--
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.
-->
Hello everyone,
... we have created Apache Fineract ${project['fineract.release.version']} release, with the artifacts below up for a vote.
It fixes the following issues: https://cwiki.apache.org/confluence/display/FINERACT/${project['fineract.release.version']}+-+Apache+Fineract
Source & Binary files : https://dist.apache.org/repos/dist/dev/fineract/${project['fineract.release.version']}/
Tag to be voted on (rc#): https://gitbox.apache.org/repos/asf?p=fineract.git;a=commit;h=refs/heads/${project['fineract.release.version']}
Fineract's KEYS containing the PGP key we used to sign the release: https://dist.apache.org/repos/dist/dev/fineract/KEYS
Note that this release contains source and binary artifacts.
This vote will be open for 72 hours:
[ ] +1 approve
[ ] +0 no opinion
[ ] -1 disapprove (and reason why)
Cheers,
${project['fineract.config.name']}
@@ -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.
-->
[FINERACT] [VOTE] 🗳️ ${project['fineract.release.version']} for release
@@ -0,0 +1,89 @@
<#--
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.
-->
<#if (project['fineract.vote'].approve.binding?size + project['fineract.vote'].approve.nonBinding?size > project['fineract.vote'].disapprove.binding?size + project['fineract.vote'].disapprove.nonBinding?size)>
Voting is now closed and has passed with the following tally,
Binding +1s: ${project['fineract.vote'].approve.binding?size}
Non binding +1s: ${project['fineract.vote'].approve.nonBinding?size}
<#else>
Voting is now closed and has not passed with the following tally,
Binding +1s: ${project['fineract.vote'].approve.binding?size}
Non binding +1s: ${project['fineract.vote'].approve.nonBinding?size}
Binding -1s: ${project['fineract.vote'].disapprove.binding?size}
Non binding -1s: ${project['fineract.vote'].disapprove.nonBinding?size}
</#if>
Here are the detailed results:
<#list project['fineract.vote'].approve.binding>
Binding +1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].approve.nonBinding>
Non binding +1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].disapprove.binding>
Binding -1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].disapprove.nonBinding>
Non binding -1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].noOpinion.binding>
Binding +0s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].noOpinion.nonBinding>
Non binding +0s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#if (project['fineract.vote'].approve.binding?size + project['fineract.vote'].approve.nonBinding?size > project['fineract.vote'].disapprove.binding?size + project['fineract.vote'].disapprove.nonBinding?size)>
Thanks to everyone who voted! I'll now continue with the rest of the release process.
<#else>
Thanks to everyone who voted! Looks like we have to repeat the vote.
</#if>
${project['fineract.config.name']}
@@ -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.
-->
[FINERACT] [VOTE] [RESULT] 🧾️ ${project['fineract.release.version']} for release
@@ -0,0 +1,43 @@
<#--
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.
-->
The Apache Fineract project is pleased to announce
the release of Apache Fineract ${project['fineract.release.version']}.
The release is available for download from
https://fineract.apache.org/#downloads
Fineract provides a reliable, robust, and affordable solution for entrepreneurs,
financial institutions, and service providers to offer financial services to the
world’s 2 billion underbanked and unbanked. Fineract is aimed at innovative mobile
and cloud-based solutions, and enables digital transaction accounts for all.
This release addressed ${project['fineract.release.issues']?size} issues.
Readme: https://github.com/apache/fineract/blob/${project['fineract.release.version']}/README.md
Release page: https://cwiki.apache.org/confluence/display/FINERACT/${project['fineract.release.version']}+-+Apache+Fineract
List of fixed issues:
https://issues.apache.org/jira/secure/ReleaseNote.jspa?version=${project['fineract.release.versionId']}&styleName=Html&projectId=${project['fineract.release.projectId']}
For more information on Apache Fineract please visit
project home page: https://fineract.apache.org
The Apache Fineract Team
@@ -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.
-->
[ANNOUNCE] Apache Fineract ${project['fineract.release.version']} Release
@@ -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.
-->
Fineract ${project['fineract.release.version']} release
@@ -0,0 +1,38 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
[INSTRUCTIONS:START]
As discussed in https://issues.apache.org/jira/browse/FINERACT-1154, now that everything is final, please do the following to remove the release branch (and just keep the tag),
and make sure that everything on the release tag is merged to develop and that e.g. git describe works:
>> git checkout develop
>> git branch -D ${project['fineract.release.version']}
>> git push origin :${project['fineract.release.version']}
>> git checkout develop
>> git checkout -b merge-${project['fineract.release.version']}
>> git merge -s recursive -Xignore-all-space ${project['fineract.release.version']}
>> git commit
>> git push <USER>
>> hub pull-request
[INSTRUCTIONS:END]
@@ -0,0 +1,28 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
[INSTRUCTIONS:START]
Finally update the https://fineract.apache.org website with the latest release details.
The website's HTML source code is available at https://github.com/apache/fineract-site.
[INSTRUCTIONS:END]
@@ -0,0 +1,39 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
[INSTRUCTIONS:START]
Before a release is done, make sure that any issues that are fixed have their fix version setup correctly.
"project = FINERACT and resolution = fixed and fixVersion is empty"
Move all unresolved JIRA issues which have this release as Fix Version to the next release
"project = FINERACT and fixVersion = ${project['fineract.release.version']} and status not in ( Resolved, Done, Accepted, Closed )"
You can also run the following query to make sure that the issues fixed for the to-be-released version look accurate:
"project = FINERACT and fixVersion = ${project['fineract.release.version']}"
Finally, check out the output of the JIRA release note tool to see which tickets are included in the release, in order to do a sanity check.
[INSTRUCTIONS:END]
@@ -0,0 +1,29 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
[INSTRUCTIONS:START]
You first need to close the release in JIRA so that the about to be released version cannot be used as "fixVersion" for new bugs anymore.
Go to JIRA "Administer project" page and follow "Versions" in left menu. Table with list of all releases should appear, click on additional menu on the right of your release and choose "Release" option.
Submit release date and you're done.
[INSTRUCTIONS:END]
@@ -0,0 +1,38 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
[INSTRUCTIONS:START]
Following are the typical things we need to verify before voting on a release candidate. And the release manager should verify them too before calling out a vote.
Make sure release artifacts are hosted at https://dist.apache.org/repos/dist/dev/fineract
* Release candidates should be in format apache-fineract-${project['fineract.release.version']}-binary.tar.gz
* Verify signatures and hashes. You may have to import the public key of the release manager to verify the signatures. (`gpg --recv-key <key id>`)
* Git tag matches the released bits (diff -rf)
* Can compile successfully from source
* Verify DISCLAIMER, NOTICE and LICENSE (year etc)
* All files have correct headers (Rat check should be clean - gradlew rat)
* No jar files in the source artifacts
* Integration tests should work
[INSTRUCTIONS:END]
@@ -0,0 +1,33 @@
<#--
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.
-->
= Changelog
== Breaking Changes
<#list issues?filter(i -> i.fields.labels?seq_contains("BreakingChange")) as issue>
* [[${issue.key}]]${issue.key} - ${issue.fields.summary}
</#list>
== Issues
<#list issues as issue>
* [[${issue.key}]]${issue.key} - ${issue.fields.summary}
</#list>
@@ -0,0 +1,26 @@
<#--
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.
-->
Changelog
=========
<#list issues as issue>
* ${issue.key} - ${issue.fields.summary}
</#list>
@@ -0,0 +1,22 @@
{
"approve": {
"binding": [
{
"name": "Some Body",
"email": "bob@apache.org"
}
]
},
"disapprove": {
"binding": [
],
"nonBinding": [
]
},
"noOpinion": {
"binding": [
],
"nonBinding": [
]
}
}
+19 -4
View File
@@ -87,12 +87,27 @@ task buildTypescriptAngularSdk(type: org.openapitools.generator.gradle.plugin.ta
]
}
licenseFormatMain.dependsOn buildJavaSdk
licenseFormatMain.dependsOn buildTypescriptAngularSdk
compileJava.dependsOn licenseFormatMain
task buildAsciidoc(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask){
generatorName = 'asciidoc'
verbose = false
validateSpec = false
skipValidateSpec = true
inputSpec = "file:///$swaggerFile"
outputDir = "$buildDir/generated/asciidoc".toString()
apiPackage = 'org.apache.fineract.client.services'
invokerPackage = 'org.apache.fineract.client'
modelPackage = 'org.apache.fineract.client.models'
dependsOn = [
':fineract-provider:resolve'
]
}
// TODO: @vidakovic we could provide even more client libs in different languages (Go, Ruby, Swift etc.)
compileJava.dependsOn buildJavaSdk, buildTypescriptAngularSdk, spotlessMiscApply
licenseMain.dependsOn(buildJavaSdk, licenseFormatMain, spotlessMiscApply)
licenseMain.dependsOn(buildTypescriptAngularSdk, licenseFormatMain, spotlessMiscApply)
licenseMain.dependsOn(buildAsciidoc, licenseFormatMain, spotlessMiscApply)
compileJava.dependsOn(licenseFormatMain, spotlessMiscApply)
java {
// keep this at Java 8, not 17; see https://issues.apache.org/jira/browse/FINERACT-1214
+6 -3
View File
@@ -1,5 +1,8 @@
:years: 2015-2022
:revnumber: 1.0.0
:revnumber: 1.7.0
:draft: true
:rootdir: ../../../..
:generated: ../../../build/generated/asciidoc
:rootdir: {asciidoctorconfigdir}/..
:generated: {asciidoctorconfigdir}/build/generated/asciidoc
:diagramsdir: {asciidoctorconfigdir}/src/docs/en/diagrams
:imagesdir: {asciidoctorconfigdir}/src/docs/en/images
:icons: font
+21 -12
View File
@@ -26,14 +26,14 @@ asciidoctorj {
version = '2.5.3'
attributes = [
version: "$project.version",
version: "${project.version}",
generated: "${buildDir}/generated/asciidoc",
imagesdir: 'images',
diagrams: 'diagrams',
imagesdir: "${projectDir}/src/docs/en/images",
diagramsdir: "${projectDir}/src/docs/en/diagrams",
years: '2015-2022',
revnumber: "$project.version".toString(),
rootdir: "$rootDir".toString(),
baseurl: 'fineract.apache.org'
revnumber: "${project.version}".toString(),
rootdir: "${rootDir}".toString(),
baseurl: 'fineract.apache.org',
]
modules {
@@ -56,6 +56,8 @@ asciidoctor {
include('index.adoc')
}
outputDir 'build/docs/html'
dependsOn ':fineract-client:buildAsciidoc'
}
// asciidoctorEpub {
@@ -68,6 +70,8 @@ asciidoctor {
// }
// outputDir 'build/docs/epub'
// ebookFormats 'epub3'
//
// dependsOn ':fineract-client:buildAsciidoc'
// }
asciidoctorPdf {
@@ -88,21 +92,26 @@ asciidoctorPdf {
// }
// }
// fontsDir "$projectDir/src/resources/fonts"
dependsOn ':fineract-client:buildAsciidoc'
}
task doc(type: Zip) {
dependsOn asciidoctor, asciidoctorPdf
into("$project.name-$project.version") {
into("fineract-${project.version}/html") {
from("$buildDir/docs/html") {
include "**/*"
exclude "**/.asciidoctor"
}
}
from ("$buildDir/docs/pdf") {
include "index.pdf"
rename {
"$project.name-$project.version\\.pdf"
into("fineract-${project.version}/pdf") {
from("$buildDir/docs/pdf") {
include "**/index.pdf"
exclude "**/.asciidoctor"
rename {
"fineract-${project.version}.pdf"
}
}
}
archiveName "$project.name-$project.version\\.zip"
archiveName "${project.name}-${project.version}.zip"
}
-1
View File
@@ -1 +0,0 @@
[appendix]
-272
View File
@@ -1,272 +0,0 @@
== Fineract Platform Software Architecture Document
=== Summary
This document captures the major architectural decisions in platform. The purpose of the document is to provide a guide to the overall structure of the platform; where it fits in the overall context of an MIS solution and its internals so that contributors can more effectively understand how changes that they are considering can be made, and the consequences of those changes.
The target audience for this report is both system integrators (who will use the document to gain an understanding of the structure of the platform and its design rationale) and platform contributors who will use the document to reason about future changes and who will update the document as the system evolves.
=== Introduction
==== The Idea
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
- Open source, owned and driven by member organisations in the community
- Enabling potential for eco-system of providers located near to MFIs
==== History
- 2006: Project intiated by Grameen Foundation
- 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/apache/fineract
- Issue tracker is https://issues.apache.org/jira/projects/FINERACT/summary
- Download from http://fineract.apache.org/
=== System Overview
.Platform System Overview
image::platform-systemview.png[]
Financial institutions deliver their services to customers through a variety of means today.
- Customers can call direct into branches (teller model)
- Customers can organise into groups (or centers) and agree to meetup at a location and time with FI staff (traditional microfinance).
- An FI might have a public facing information portal that customers can use for variety of reasons including account management (online banking).
- An FI might be integrated into a ATM/POS/Card services network that the customer can use.
- An FI might be integrated with a mobile money operator and support mobile money services for customer (present/future microfinance).
- An FI might use third party agents to sell on products/services from other banks/FIs.
As illustrated in the above diagram, the various stakeholders leverage business apps to perform specific customer or FI related actions. The functionality contained in these business apps can be bundled up and packaged in any way. In the diagram, several of the apps may be combined into one app or any one of the blocks representing an app could be further broken up as needed.
The platform is the core engine of the MIS. It hides alot of the complexity that exists in the business and technical domains needed for an MIS in FIs behind a relatively simple API. It is this API that frees up app developers to innovate and produce apps that can be as general or as bespoke as FIs need them to be.
=== Functional Overview
As ALL capabilities of the platform are exposed through an API, The API docs are the best place to view a detailed breakdown of what the platform does. See online API Documentation.
.Platform Functional Overview
image::platform-categories.png[]
At a higher level though we see the capabilities fall into the following categories:
* Infrastructure
** Codes
** Extensible Data Tables
** Reporting
* User Administration
** Users
** Roles
** Permissions
* Organisation Modelling
** Offices
** Staff
** Currency
* Product Configuration
** Charges
** Loan Products
** Deposit Products
* Client Data
** Know Your Client (KYC)
* Portfolio Management
** Loan Accounts
** Deposit Accounts
** Client/Groups
* GL Account Management
** Chart of Accounts
** General Ledger
=== Technology
* 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
** Spring Boot
** Spring Security
** Spring Data (JPA) backed by EclipseLink
* MySQL: http://www.oracle.com/us/products/mysql/overview/index.html
* PostgreSQL
=== Principles
==== RESTful API
The platform exposes all its functionality via a *practically-RESTful API*, that communicates using JSON.
We use the term *practically-RESTful* in order to make it clear we are not trying to be fully REST compliant but still maintain important RESTful attributes like:
* Stateless: platform maintains no conversational or session-based state. The result of this is ability to scale horizontally with ease.
* Resource-oriented: API is focussed around set of resources using HTTP vocabulary and conventions e.g GET, PUT, POST, DELETE, HTTP status codes. This results in a simple and consistent API for clients.
See online API Documentation for more detail.
==== Multi-tenanted
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).
==== Extensible
Whilst each tenant will have a set of core tables, the platform tables can be extended in different ways for each tenant through the use of Data tables functionality.
==== Command Query Seperation
We seperate *commands* (that change data) from *queries* (that read data).
Why? There are numerous reasons for choosing this approach which at present is not an attempt at full blown CQRS. The main advantages at present are:
* State changing commands are persisted providing an audit of all state changes.
* Used to support a general approach to *maker-checker*.
* State changing commands use the Object-Oriented paradign (and hence ORM) whilst querys can stay in the data paradigm.
==== Maker-Checker
Also known as *four-eyes principal*. Enables apps to support a maker-checker style workflow process. Commands that pass validation will be persisted. Maker-checker can be enabled/disabled at fine-grained level for any state changing API.
Fine grained access control
A fine grained permission is associated with each API. Administrators have fine grained control over what roles or users have access to.
=== Code Packaging
The intention is for platform code to be packaged in a vertical slice way (as opposed to layers).
Source code starts from https://github.com/apache/fineract/tree/develop/fineract-provider/src/main/java/org/apache/fineract
* accounting
* useradministration
* infrastructure
* portfolio
** charge
** client
** fund
** loanaccount
* accounting
Within each vertical slice is some common packaging structure:
* api - XXXApiResource.java - REST api implementation files
* handler - XXXCommandHandler.java - specific handlers invoked
* service - contains read + write services for functional area
* domain - OO concepts for the functional area
* data - Data concepts for the area
* serialization - ability to convert from/to API JSON for functional area
=== Design Overview
NOTE: The implementation of the platform code to process commands through handlers whilst supporting maker-checker and authorisation checks is a little bit convoluted at present and is an area pin-pointed for clean up to make it easier to on board new platform developers. In the mean time below content is used to explain its workings at present.
.CQRS
image::command-query.png[]
Taking into account example shown above for the *users* resource.
* Query: GET /users
* HTTPS API: retrieveAll method on *org.apache.fineract.useradministration.api.UsersApiResource* invoked
* UsersApiResource.retrieveAll: Check user has permission to access this resources data.
* UsersApiResource.retrieveAll: Use 'read service' to fetch all users data ('read services' execute simple SQL queries against Database using JDBC)
* UsersApiResource.retrieveAll: Data returned to coverted into JSON response
* Command: POST /users (Note: data passed in request body)
* HTTPS API: create method on org.apache.fineract.useradministration.api.UsersApiResource invoked
.UsersApiResource.create
[source,java]
----
include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java[lines=168..188]
----
.Create a CommandWrapper object that represents this create user command and JSON request body. Pass off responsiblity for processing to PortfolioCommandSourceWritePlatformService.logCommandSource
[source,java]
----
include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java[lines=69..129]
----
.Check user has permission for this action. if ok, a) parse the json request body, b) create a JsonCommand object to wrap the command details, c) use CommandProcessingService to handle command
[source,java]
----
include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java[lines=84..150]
----
NOTE: if a RollbackTransactionAsCommandIsNotApprovedByCheckerException occurs at this point. The original transaction will of been aborted and we only log an entry for the command in the audit table setting its status as 'Pending'.
* Check that if maker-checker configuration enabled for this action. If yes and this is not a 'checker' approving the command - rollback at the end. We rollback at the end in order to test if the command will pass 'domain validation' which requires commit to database for full check.
* findCommandHandler - Find the correct Hanlder to process this command.
* Process command using handler (In transactional scope).
* CommandSource object created/updated with all details for logging to 'm_portfolio_command_source' table.
* In update scenario, we check to see if there where really any changes/updates. If so only JSON for changes is stored in audit log.
=== Database support
Fineract supports multiple databases:
* MySQL compatible databases (e.g. MariaDB)
* PostgreSQL
The platform differentiates between these database types in certain cases when there's a need to use some database specific tooling. To do so, the platform examines the JDBC driver used for running the platform and tries to determine which database is being used.
The currently supported JDBC driver and corresponding mappings can be found below.
[cols="1,1"]
|===
|*JDBC driver class name*
|*Resolved database type*
|`org.mariadb.jdbc.Driver`
|MySQL
|`com.mysql.jdbc.Driver`
|MySQL
|`org.postgresql.Driver`
|PostgreSQL
|===
The actual code can be found in the `DatabaseTypeResolver` class.
=== Data-access layer
The data-access layer of Fineract is implemented by using JPA (Java Persistence API) with the EclipseLink provider.
Despite the fact that JPA is used quite extensively in the system, there are cases where the performance is a key element for an operation therefore you can easily find native SQLs as well.
The data-access layer of Fineract is compatible with different databases. Since a lot of the native queries are using specific database functions, a wrapper class - `DatabaseSpecificSQLGenerator` - has been introduced to handle these database specifics. Whenever there's a need to rely on new database level functions, make sure to extend this class and implement the specific functions provided by the database.
Fineract has been developed for 10+ years by the community and unfortunately there are places where entity relationships are configured with `EAGER` fetching strategy. This must not confuse anybody. The long-term goal is to use the `LAZY` fetching strategy for every single relationship. If you're about to introduce a new one, make sure to use `LAZY` as a fetching strategy, otherwise your PR will be rejected.
=== Database schema migration
As for every system, the database structure will and need to evolve over time. Fineract is no different. Originally for Fineract, Flyway was used until Fineract 1.6.x.
After 1.6.x, PostgreSQL support was added to the platform hence there was a need to make the data-access layer and the schema migration as database independent as possible. Becuase of that, from Fineract 1.7.0, Flyway is not used anymore but Liquibase is.
Some of the changesets in the Liquibase changelogs have database specifics into it but they only run for the relevant databases. This is controller by Liquibase contexts.
The currently available Liquibase contexts are:
* `mysql` - only set when the database is a MySQL compatible database (e.g. MariaDB)
* `postgresql` - only set when the database is a PostgreSQL database
* configured Spring active profiles
* `tenant_store_db` - only set when the database migration runs the Tenant Store upgrade
* `tenant_db` - only set when the database migration runs the Tenant upgrade
* `initial_switch` - this is a technical context and should *NOT* be used
The switch from Flyway (1.6.x) to Liquibase (1.7.x) was planned to be as smooth as possible so there's no need for manual work hence the behavior is described as following:
* If the database is empty, Liquibase will create the database schema from scratch
* If the database contains the latest Fineract 1.6.x database structure which was previously migrated with Flyway. Liquibase will seamlessly upgrade it to the latest version. Note: the Flyway related 2 database tables are left as they are and are not deleted.
* If the database contains an earlier version of the database structure than Fineract 1.6.x. Liquibase will *NOT* do anything and *will fail the application during startup*. The proper approach in this case is to first upgrade your application version to the latest Fineract 1.6.x so that the latest Flyway changes are executed and then upgrade to the newer Fineract version where Liquibase will seamlessly take over the database upgrades.
@@ -0,0 +1,5 @@
// NOTE: no section title needed here!
include::properties.adoc[leveloffset=+1]
include::software.adoc[leveloffset=+1]
@@ -0,0 +1,22 @@
= Authentication Properties
.Authentication Properties
|===
|Name |Env Variable |Default Value |Description
|fineract.security.basicauth.enabled
|FINERACT_SECURITY_BASICAUTH_ENABLED
|true
|TBD
|fineract.security.oauth.enabled
|FINERACT_SECURITY_OAUTH_ENABLED
|false
|TBD
|fineract.security.2fa.enabled
|FINERACT_SECURITY_2FA_ENABLED
|false
|TBD
|===
@@ -0,0 +1,16 @@
= Cache Properties
.Cache Properties
|===
|Name |Env Variable |Default Value |Description
|Column 1, row 1
|Column 2, row 1
|Column 3, row 1
|TBD
|Column 1, row 2
|Column 2, row 2
|Column 3, row 2
|TBD
|===
@@ -0,0 +1,51 @@
= Tenant Database Properties
.Tenant Database Properties
|===
|Name |Env Variable |Default Value |Description
|fineract.tenant.host
|FINERACT_DEFAULT_TENANTDB_HOSTNAME
|localhost
|TBD
|fineract.tenant.port
|FINERACT_DEFAULT_TENANTDB_PORT
|3306
|TBD
|fineract.tenant.username
|FINERACT_DEFAULT_TENANTDB_UID
|root
|TBD
|fineract.tenant.password
|FINERACT_DEFAULT_TENANTDB_PWD
|mysql
|TBD
|fineract.tenant.parameters
|FINERACT_DEFAULT_TENANTDB_CONN_PARAMS
|
|TBD
|fineract.tenant.timezone
|FINERACT_DEFAULT_TENANTDB_TIMEZONE
|Asia/Kolkata
|TBD
|fineract.tenant.identifier
|FINERACT_DEFAULT_TENANTDB_IDENTIFIER
|default
|TBD
|fineract.tenant.name
|FINERACT_DEFAULT_TENANTDB_NAME
|fineract_default
|TBD
|fineract.tenant.description
|FINERACT_DEFAULT_TENANTDB_DESCRIPTION
|Default Demo Tenant
|TBD
|===
@@ -0,0 +1,116 @@
= Hikari Connection Pool Properties
.Hikari Connection Pool Properties
|===
|Name |Env Variable |Default Value |Description
|spring.datasource.hikari.driverClassName
|FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME
|org.mariadb.jdbc.Driver
|TBD
|spring.datasource.hikari.jdbcUrl
|FINERACT_HIKARI_JDBC_URL
|jdbc:mariadb://localhost:3306/fineract_tenants
|TBD
|spring.datasource.hikari.username
|FINERACT_HIKARI_USERNAME
|root
|TBD
|spring.datasource.hikari.password
|FINERACT_HIKARI_PASSWORD
|mysql
|TBD
|spring.datasource.hikari.minimumIdle
|FINERACT_HIKARI_MINIMUM_IDLE
|3
|TBD
|spring.datasource.hikari.maximumPoolSize
|FINERACT_HIKARI_MAXIMUM_POOL_SIZE
|10
|TBD
|spring.datasource.hikari.idleTimeout
|FINERACT_HIKARI_IDLE_TIMEOUT
|60000
|TBD
|spring.datasource.hikari.connectionTimeout
|FINERACT_HIKARI_CONNECTION_TIMEOUT
|20000
|TBD
|spring.datasource.hikari.connectionTestquery
|FINERACT_HIKARI_TEST_QUERY
|SELECT 1
|TBD
|spring.datasource.hikari.autoCommit
|FINERACT_HIKARI_AUTO_COMMIT
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['cachePrepStmts']
|FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['prepStmtCacheSize']
|FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE
|250
|TBD
|spring.datasource.hikari.dataSourceProperties['prepStmtCacheSqlLimit']
|FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT
|2048
|TBD
|spring.datasource.hikari.dataSourceProperties['useServerPrepStmts']
|FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['useLocalSessionState']
|FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['rewriteBatchedStatements']
|FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['cacheResultSetMetadata']
|FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['cacheServerConfiguration']
|FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['elideSetAutoCommits']
|FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['maintainTimeStats']
|FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS
|false
|TBD
|spring.datasource.hikari.dataSourceProperties['logSlowQueries']
|FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES
|true
|TBD
|spring.datasource.hikari.dataSourceProperties['dumpQueriesOnException']
|FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION
|true
|TBD
|===
@@ -0,0 +1,36 @@
= SSL Properties
.SSL Properties
|===
|Name |Env Variable |Default Value |Description
|server.ssl.enabled
|FINERACT_SERVER_SSL_ENABLED
|true
|TBD
|server.ssl.protocol
|FINERACT_SERVER_SSL_PROTOCOL
|TLS
|TBD
|server.ssl.ciphers
|FINERACT_SERVER_SSL_CIPHERS
|TLS_RSA_WITH_AES_128_CBC_SHA256
|TBD
|server.ssl.enabled-protocols
|FINERACT_SERVER_SSL_PROTOCOLS
|TLSv1.2
|TBD
|server.ssl.key-store
|FINERACT_SERVER_SSL_KEY_STORE
|classpath:keystore.jks
|TBD
|server.ssl.key-store-password
|FINERACT_SERVER_SSL_KEY_STORE_PASSWORD
|openmf
|TBD
|===
@@ -0,0 +1,42 @@
= Tomcat Properties
.Tomcat Properties
|===
|Name |Env Variable |Default Value |Description
|server.tomcat.accept-count
|FINERACT_SERVER_TOMCAT_ACCEPT_COUNT
|100
|TBD
|server.tomcat.accesslog.enabled
|FINERACT_SERVER_TOMCAT_ACCESSLOG_ENABLED
|false
|TBD
|server.tomcat.max-connections
|FINERACT_SERVER_TOMCAT_MAX_CONNECTIONS
|8192
|TBD
|server.tomcat.max-http-form-post-size
|FINERACT_SERVER_TOMCAT_MAX_HTTP_FORM_POST_SIZE
|2MB
|TBD
|server.tomcat.max-keep-alive-requests
|FINERACT_SERVER_TOMCAT_MAX_KEEP_ALIVE_REQUESTS
|100
|TBD
|server.tomcat.threads.max
|FINERACT_SERVER_TOMCAT_THREADS_MAX
|200
|TBD
|server.tomcat.threads.min-spare
|FINERACT_SERVER_TOMCAT_THREADS_MIN_SPARE
|10
|TBD
|===
@@ -0,0 +1,16 @@
[appendix]
= Fineract Application Properties
TBD
include::properties-database.adoc[leveloffset=+1]
include::properties-pool.adoc[leveloffset=+1]
include::properties-ssl.adoc[leveloffset=+1]
include::properties-authentication.adoc[leveloffset=+1]
include::properties-tomcat.adoc[leveloffset=+1]
// include::properties-cache.adoc[leveloffset=+1]
@@ -0,0 +1,4 @@
[appendix]
= Third Party Software
TBD
@@ -0,0 +1,42 @@
= Design Overview
NOTE: The implementation of the platform code to process commands through handlers whilst supporting maker-checker and authorisation checks is a little bit convoluted at present and is an area pin-pointed for clean up to make it easier to on board new platform developers. In the mean time below content is used to explain its workings at present.
.CQRS
image::{imagesdir}/command-query.png[]
Taking into account example shown above for the *users* resource.
* Query: GET /users
* HTTPS API: retrieveAll method on *org.apache.fineract.useradministration.api.UsersApiResource* invoked
* UsersApiResource.retrieveAll: Check user has permission to access this resources data.
* UsersApiResource.retrieveAll: Use 'read service' to fetch all users data ('read services' execute simple SQL queries against Database using JDBC)
* UsersApiResource.retrieveAll: Data returned to coverted into JSON response
* Command: POST /users (Note: data passed in request body)
* HTTPS API: create method on org.apache.fineract.useradministration.api.UsersApiResource invoked
.UsersApiResource.create
[source,java]
----
include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java[lines=168..188]
----
.Create a CommandWrapper object that represents this create user command and JSON request body. Pass off responsiblity for processing to PortfolioCommandSourceWritePlatformService.logCommandSource
[source,java]
----
include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java[lines=69..129]
----
.Check user has permission for this action. if ok, a) parse the json request body, b) create a JsonCommand object to wrap the command details, c) use CommandProcessingService to handle command
[source,java]
----
include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java[lines=84..150]
----
NOTE: if a RollbackTransactionAsCommandIsNotApprovedByCheckerException occurs at this point. The original transaction will of been aborted and we only log an entry for the command in the audit table setting its status as 'Pending'.
* Check that if maker-checker configuration enabled for this action. If yes and this is not a 'checker' approving the command - rollback at the end. We rollback at the end in order to test if the command will pass 'domain validation' which requires commit to database for full check.
* findCommandHandler - Find the correct Hanlder to process this command.
* Process command using handler (In transactional scope).
* CommandSource object created/updated with all details for logging to 'm_portfolio_command_source' table.
* In update scenario, we check to see if there where really any changes/updates. If so only JSON for changes is stored in audit log.
@@ -0,0 +1,41 @@
= Functional Overview
As ALL capabilities of the platform are exposed through an API, The API docs are the best place to view a detailed breakdown of what the platform does. See online API Documentation.
.Platform Functional Overview
image::{imagesdir}/platform-categories.png[]
At a higher level though we see the capabilities fall into the following categories:
* Infrastructure
** Codes
** Extensible Data Tables
** Reporting
* User Administration
** Users
** Roles
** Permissions
* Organisation Modelling
** Offices
** Staff
** Currency
* Product Configuration
** Charges
** Loan Products
** Deposit Products
* Client Data
** Know Your Client (KYC)
* Portfolio Management
** Loan Accounts
** Deposit Accounts
** Client/Groups
* GL Account Management
** Chart of Accounts
** General Ledger
@@ -0,0 +1,18 @@
= History
== The Idea
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
* Open source, owned and driven by member organisations in the community
* Enabling potential for eco-system of providers located near to MFIs
== Timeline
* 2006: Project intiated by Grameen Foundation
* 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
@@ -0,0 +1,25 @@
= Architecture
This document captures the major architectural decisions in platform. The purpose of the document is to provide a guide to the overall structure of the platform; where it fits in the overall context of an MIS solution and its internals so that contributors can more effectively understand how changes that they are considering can be made, and the consequences of those changes.
The target audience for this report is both system integrators (who will use the document to gain an understanding of the structure of the platform and its design rationale) and platform contributors who will use the document to reason about future changes and who will update the document as the system evolves.
include::history.adoc[leveloffset=+1]
include::resources.adoc[leveloffset=+1]
include::system.adoc[leveloffset=+1]
include::functional.adoc[leveloffset=+1]
include::principles.adoc[leveloffset=+1]
include::design.adoc[leveloffset=+1]
include::persistence.adoc[leveloffset=+1]
include::validation.adoc[leveloffset=+1]
include::technology.adoc[leveloffset=+1]
include::modules.adoc[leveloffset=+1]
@@ -1,8 +1,8 @@
== Modules
= Modules
Currently modules are a proof of concept feature in Fineract.
=== How to create a Fineract module
== How to create a Fineract module
NOTE: At the moment the only module we have that follows these stricter guidelines is the example module we describe in detail in the next section. In the future we will try to split up Fineract's monolithic code base and move features (like loan product, accounts etc.) to separate modules as described here. Before that happens we need to do some code cleanups first.
@@ -17,7 +17,7 @@ The final folder and file structure should look somewhat like this:
+
[plantuml, format=svg]
----
include::diagrams/modules-folder-structure.puml[]
include::{diagramsdir}/modules-folder-structure.puml[]
----
1. Make sure that your new modules are present in settings.gradle:
@@ -38,7 +38,7 @@ include ':fineract-doc'
----
<.> The `settings.gradle` file should contain something like this (and following).
=== How to replace an existing Fineract service
== How to replace an existing Fineract service
Creating customizations for Fineract services is easy. The method described here will work both with our future module guidelines (aka "clean room" modules) and with the intermediary solution we will put in place to avoid major refactorings.
@@ -61,7 +61,7 @@ The final folder and file structure should look somewhat like this:
+
[plantuml, format=svg]
----
include::diagrams/custom-folder-structure.puml[]
include::{diagramsdir}/custom-folder-structure.puml[]
----
+
Please make sure that your `service` module's build.gradle file has a unique group:
@@ -107,9 +107,9 @@ dependencies {
IMPORTANT: Do not include your custom module in `fineract-provider`'s dependency.gradle file. This creates a circular dependency and will fail your build. Instead you have to add your JAR file e. g. to the Docker image (in Fineract's libs folder; similar like we do it with Pentaho reporting). This setup will give you the best developer experience for now with proper source references until we have separate JAR files ready on Maven Central. In the next section we describe the "clean room" (read: where we want to go) type of modules and their customization/replacement; so don't be confused if you see the dependency appearing in `fineract-provider`'s dependency.gradle, in the future you'll be able to do that.
=== Example
== Example
==== Dummy Module Structure
=== Dummy Module Structure
We've created a demonstration how modules are supposed to be used. A module has usually at least two sub-modules:
@@ -179,7 +179,7 @@ dependencies {
Hello: DEFAULT DUMMY!
----
==== Replace the default implementation of DummyService
=== Replace the default implementation of DummyService
This is why we created module `foo` in folder `module/foo`. There's only only one sub-module that contains an implementation of `DummyService` that will replace the default implementation:
@@ -207,7 +207,7 @@ dependencies {
Hello: Foo!
----
==== Testing
=== Testing
There's a simple unit test in `fineract-provider` that demonstrates both scenarios (without any override of the default implementation and with an overriding alternative implementation)
@@ -237,14 +237,14 @@ The important part in the `foo` configuration is this line:
include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java[lines=26..26]
----
==== Deployment
=== Deployment
Modules (better: the JAR files) only need to be dropped in Fineract's `libs` folder. Dynamic loading of external JARs is provided since Fineract version 1.5.0.
==== How can I start replacing services now
=== How can I start replacing services now
As said the "clean room" modules will take a while to arrive. In the meanwhile we'll prepare the existing monolithic code base for pluggability. As a proof of concept the services `org.apache.fineract.portfolio.note.service.NoteReadPlatformService` and `org.apache.fineract.portfolio.note.service.NoteWritePlatformService` can be replaced/overriden by custom implementations. We'll add more shortly and will list them here.
As said the "clean room" modules will take a while to arrive. In the meanwhile we'll prepare the existing monolithic code base for pluggability. As a proof of concept the services `org.apache.fineract.portfolio.note.service.NoteReadPlatformService` and `org.apache.fineract.portfolio.note.service.NoteWritePlatformService` can be replaced/overriden by custom implementations. We'll add more shortly and will list them here. Reach out on the mailing list if you need a specific service to be replaceable.
==== Outlook
=== Outlook
If this proof of concept is accepted we could refactor Fineract's services one by one and rearrange them in the proposed module structure. As discussed at ApacheCon 2021 there will be most likely some work to do to properly extract those modules (loan product, client, savings account etc.) from the monolithic code base. The main challenge are cross-dependencies between the _modules_, but there are major benefits if we split up the code like this. One - as demonstrated - is the replacement of existing default functionality. But with modules we'll also be able to finally publish Fineract JAR files to Maven Central. Those JAR files can be used to write extensions and customizations *WITHOUT* forking the whole Fineract Github repository. With modules users will also be able to create custom Fineract distributions, e. g. slimmed down to contain only the bare minimum needed in production.
@@ -0,0 +1,64 @@
= Persistence
TBD
== Database support
Fineract supports multiple databases:
* MySQL compatible databases (e.g. MariaDB)
* PostgreSQL
The platform differentiates between these database types in certain cases when there's a need to use some database specific tooling. To do so, the platform examines the JDBC driver used for running the platform and tries to determine which database is being used.
The currently supported JDBC driver and corresponding mappings can be found below.
[cols="1,1"]
|===
|*JDBC driver class name*
|*Resolved database type*
|`org.mariadb.jdbc.Driver`
|MySQL
|`com.mysql.jdbc.Driver`
|MySQL
|`org.postgresql.Driver`
|PostgreSQL
|===
The actual code can be found in the `DatabaseTypeResolver` class.
== Data-access layer
The data-access layer of Fineract is implemented by using JPA (Java Persistence API) with the EclipseLink provider.
Despite the fact that JPA is used quite extensively in the system, there are cases where the performance is a key element for an operation therefore you can easily find native SQLs as well.
The data-access layer of Fineract is compatible with different databases. Since a lot of the native queries are using specific database functions, a wrapper class - `DatabaseSpecificSQLGenerator` - has been introduced to handle these database specifics. Whenever there's a need to rely on new database level functions, make sure to extend this class and implement the specific functions provided by the database.
Fineract has been developed for 10+ years by the community and unfortunately there are places where entity relationships are configured with `EAGER` fetching strategy. This must not confuse anybody. The long-term goal is to use the `LAZY` fetching strategy for every single relationship. If you're about to introduce a new one, make sure to use `LAZY` as a fetching strategy, otherwise your PR will be rejected.
== Database schema migration
As for every system, the database structure will and need to evolve over time. Fineract is no different. Originally for Fineract, Flyway was used until Fineract 1.6.x.
After 1.6.x, PostgreSQL support was added to the platform hence there was a need to make the data-access layer and the schema migration as database independent as possible. Becuase of that, from Fineract 1.7.0, Flyway is not used anymore but Liquibase is.
Some of the changesets in the Liquibase changelogs have database specifics into it but they only run for the relevant databases. This is controller by Liquibase contexts.
The currently available Liquibase contexts are:
* `mysql` - only set when the database is a MySQL compatible database (e.g. MariaDB)
* `postgresql` - only set when the database is a PostgreSQL database
* configured Spring active profiles
* `tenant_store_db` - only set when the database migration runs the Tenant Store upgrade
* `tenant_db` - only set when the database migration runs the Tenant upgrade
* `initial_switch` - this is a technical context and should *NOT* be used
The switch from Flyway (1.6.x) to Liquibase (1.7.x) was planned to be as smooth as possible so there's no need for manual work hence the behavior is described as following:
* If the database is empty, Liquibase will create the database schema from scratch
* If the database contains the latest Fineract 1.6.x database structure which was previously migrated with Flyway. Liquibase will seamlessly upgrade it to the latest version. Note: the Flyway related 2 database tables are left as they are and are not deleted.
* If the database contains an earlier version of the database structure than Fineract 1.6.x. Liquibase will *NOT* do anything and *will fail the application during startup*. The proper approach in this case is to first upgrade your application version to the latest Fineract 1.6.x so that the latest Flyway changes are executed and then upgrade to the newer Fineract version where Liquibase will seamlessly take over the database upgrades.
@@ -0,0 +1,63 @@
= Principles
== RESTful API
The platform exposes all its functionality via a *practically-RESTful API*, that communicates using JSON.
We use the term *practically-RESTful* in order to make it clear we are not trying to be fully REST compliant but still maintain important RESTful attributes like:
* Stateless: platform maintains no conversational or session-based state. The result of this is ability to scale horizontally with ease.
* Resource-oriented: API is focussed around set of resources using HTTP vocabulary and conventions e.g GET, PUT, POST, DELETE, HTTP status codes. This results in a simple and consistent API for clients.
See online API Documentation for more detail.
== Multi-tenanted
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).
== Extensible
Whilst each tenant will have a set of core tables, the platform tables can be extended in different ways for each tenant through the use of Data tables functionality.
== Command Query Seperation
We seperate *commands* (that change data) from *queries* (that read data).
Why? There are numerous reasons for choosing this approach which at present is not an attempt at full blown CQRS. The main advantages at present are:
* State changing commands are persisted providing an audit of all state changes.
* Used to support a general approach to *maker-checker*.
* State changing commands use the Object-Oriented paradign (and hence ORM) whilst querys can stay in the data paradigm.
== Maker-Checker
Also known as *four-eyes principal*. Enables apps to support a maker-checker style workflow process. Commands that pass validation will be persisted. Maker-checker can be enabled/disabled at fine-grained level for any state changing API.
Fine grained access control
A fine grained permission is associated with each API. Administrators have fine grained control over what roles or users have access to.
== Package Structure
The intention is for platform code to be packaged in a vertical slice way (as opposed to layers).
Source code starts from https://github.com/apache/fineract/tree/develop/fineract-provider/src/main/java/org/apache/fineract
* accounting
* useradministration
* infrastructure
* portfolio
** charge
** client
** fund
** loanaccount
* accounting
Within each vertical slice is some common packaging structure:
* api - XXXApiResource.java - REST api implementation files
* handler - XXXCommandHandler.java - specific handlers invoked
* service - contains read + write services for functional area
* domain - OO concepts for the functional area
* data - Data concepts for the area
* serialization - ability to convert from/to API JSON for functional area
@@ -0,0 +1,5 @@
= Resources
* 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/
@@ -0,0 +1,18 @@
= System Overview
.Platform System Overview
image::{imagesdir}/platform-systemview.png[]
Financial institutions deliver their services to customers through a variety of means today.
* Customers can call direct into branches (teller model)
* Customers can organise into groups (or centers) and agree to meetup at a location and time with FI staff (traditional microfinance).
* An FI might have a public facing information portal that customers can use for variety of reasons including account management (online banking).
* An FI might be integrated into a ATM/POS/Card services network that the customer can use.
* An FI might be integrated with a mobile money operator and support mobile money services for customer (present/future microfinance).
* An FI might use third party agents to sell on products/services from other banks/FIs.
As illustrated in the above diagram, the various stakeholders leverage business apps to perform specific customer or FI related actions. The functionality contained in these business apps can be bundled up and packaged in any way. In the diagram, several of the apps may be combined into one app or any one of the blocks representing an app could be further broken up as needed.
The platform is the core engine of the MIS. It hides alot of the complexity that exists in the business and technical domains needed for an MIS in FIs behind a relatively simple API. It is this API that frees up app developers to innovate and produce apps that can be as general or as bespoke as FIs need them to be.
@@ -0,0 +1,14 @@
= Technology
* 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
** Spring Boot
** Spring Security
** Spring Data (JPA) backed by EclipseLink
* MySQL: http://www.oracle.com/us/products/mysql/overview/index.html
* PostgreSQL
TBD
@@ -1,8 +1,8 @@
== Validation
= Validation
=== Programmatic
== Programmatic
Use the [DataValidatorBuilder](https://github.com/apache/fineract/search?q=%22class+DataValidatorBuilder%22), e.g. like so:
Use the https://github.com/apache/fineract/search?q=%22class+DataValidatorBuilder%22[DataValidatorBuilder], e.g. like so:
[source,java]
----
@@ -12,8 +12,7 @@ new DataValidatorBuilder().resource("fileUpload")
.throwValidationErrors();
----
Such code is often encapsulated in [`*Validator`](https://github.com/apache/fineract/search?q=Validator)
classes (if more than a few lines, and/or reused from several places; avoid copy/paste), like so:
Such code is often encapsulated in https://github.com/apache/fineract/search?q=Validator[`*Validator`] classes (if more than a few lines, and/or reused from several places; avoid copy/paste), like so:
[source,java]
----
@@ -28,7 +27,6 @@ public class YourThingValidator {
----
=== Declarative
== Declarative
[FINERACT-1229](https://issues.apache.org/jira/browse/FINERACT-1229) is an open issue about adopting
Bean Validation for _declarative_ instead of _programmatic_ (as above) validation. Contributions welcome!
[FINERACT-1229](https://issues.apache.org/jira/browse/FINERACT-1229) is an open issue about adopting Bean Validation for _declarative_ instead of _programmatic_ (as above) validation. Contributions welcome!
@@ -0,0 +1,5 @@
= Apache Software Foundation Infrastructure
We can order a server from Apache's infrastructure team and deploy a demo instance...
TBD
@@ -0,0 +1,25 @@
= Application Server
== Tomcat
TBD
== Undertow
TBD
== Jetty
TBD
== JBoss
TBD
== Weblogic
TBD
== Payara
TBD
@@ -0,0 +1,3 @@
= AWS
TBD
@@ -0,0 +1,5 @@
= Google Cloud
The https://www.fineract.dev demo server runs on Google Cloud.
https://docs.google.com/presentation/d/1-VP4bNkc5kZ3B0yme_vYLiY1qpswnfz8ainnX5fp3l8/[The Running Fineract.dev, SRE style presentation] given at _ApacheCon 2020_ has some related background.
@@ -1,24 +1,4 @@
== Deployment
=== Plugins
Apache Fineract is extensible through plugin JARs (https://issues.apache.org/jira/browse/FINERACT-1177[FINERACT-1177]; based on
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html[Spring Boot's support]). To launch Fineract with plugin JARs in `libs/*.jar`, use:
----
java -Dloader.path=libs/ -jar fineract-provider.jar
----
The Fineract "Docker" container image's `ENTRYPOINT` uses this, see our `Dockerfile`. You could therefore build your customized Fineract distribution container image with your own `Dockerfile` using e.g. `FROM apache/fineract:latest` and then drop some plugin JARs into `/app/libs/`.
The WAR distribution does not directly support such plugins, but one could "explode" the WAR and drop JARs into `WEB-INF/lib`; if you know what you are doing, and feel nostalgic of the 1990s still using WARs, instead of the recommended modern Spring Boot distribution.
Here is a list of known 3rd-party plugin projects which can be dropped into `libs/`:
* https://github.com/vorburger/fineract-pentaho
=== HTTPS
= HTTPS
Because Apache Fineract deals with customer sensitive personally identifiable information (PII), it very strongly encourages all developers, implementors and end-users to always only use HTTPS. This is why it does not run on HTTP even for local development and enforces use of HTTPS.
@@ -31,10 +11,3 @@ Such products, when correctly configured, add the conventional `X-Forwarded-For`
Alternatively, you could replace the built-in default SSL certificate with one you obtained from a Certificate Authority. We currently do not document how to do this, because we do not recommend this approach, as its cumbersome to configure and support and less secure than a managed auto rotating solution.
The Fineract API client supports an insecure mode (`FineractClient.Builder#insecure()`), and API users such as mobile apps may expose Settings to let end-users accept the self signed certificate. This should always be used for testing only, never in production.
=== Google Cloud
The https://www.fineract.dev demo server runs on Google Cloud.
https://docs.google.com/presentation/d/1-VP4bNkc5kZ3B0yme_vYLiY1qpswnfz8ainnX5fp3l8/[The Running Fineract.dev, SRE style presentation] given at _ApacheCon 2020_ has some related background.
@@ -0,0 +1,17 @@
= Deployment
include::plugins.adoc[leveloffset=+1]
include::https.adoc[leveloffset=+1]
include::local.adoc[leveloffset=+1]
include::appserver.adoc[leveloffset=+1]
include::kubernetes.adoc[leveloffset=+1]
include::aws.adoc[leveloffset=+1]
include::google.adoc[leveloffset=+1]
include::apache.adoc[leveloffset=+1]
@@ -0,0 +1,3 @@
= Kubernetes
TBD
@@ -0,0 +1,3 @@
= Docker Compose
TBD
@@ -0,0 +1,18 @@
= Plugins
Apache Fineract is extensible through plugin JARs (https://issues.apache.org/jira/browse/FINERACT-1177[FINERACT-1177]; based on
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html[Spring Boot's support]). To launch Fineract with plugin JARs in `libs/*.jar`, use:
----
java -Dloader.path=libs/ -jar fineract-provider.jar
----
The Fineract "Docker" container image's `ENTRYPOINT` uses this, see our `Dockerfile`. You could therefore build your customized Fineract distribution container image with your own `Dockerfile` using e.g. `FROM apache/fineract:latest` and then drop some plugin JARs into `/app/libs/`.
The WAR distribution does not directly support such plugins, but one could "explode" the WAR and drop JARs into `WEB-INF/lib`; if you know what you are doing, and feel nostalgic of the 1990s still using WARs, instead of the recommended modern Spring Boot distribution.
Here is a list of known 3rd-party plugin projects which can be dropped into `libs/`:
* https://github.com/vorburger/fineract-pentaho
NOTE: The reporting module became our first module experiment out of necessity. We are currently developing a strategy to split up even more internals of Fineract into proper modules. Those that have an incompatible license will be hosted in a separate Git repository (probably on Github under the Mifos organisation). We'll send out an announcement as soon as we have more to say on this topic.
@@ -0,0 +1,3 @@
= Docker Compose
TBD
@@ -0,0 +1,3 @@
= Podman
TBD
@@ -0,0 +1,3 @@
= Rancher Docker Desktop
TBD
@@ -0,0 +1,9 @@
= Docker
TBD
include::docker-compose.adoc[leveloffset=+1]
include::docker-podman.adoc[leveloffset=+1]
include::docker-rancher.adoc[leveloffset=+1]
@@ -0,0 +1,9 @@
= Configuration
Configure your personal details properly:
* email
* name
* GPG key
TBD
@@ -0,0 +1,17 @@
= Fork
Make sure to disable Github Actions in your forked repository. See: https://github.com/vidakovic/fineract/settings/actions
TBD
== Upstream Configuration
TBD
== Syncing Upstream
TBD
== Pull Requests
TBD
@@ -0,0 +1,3 @@
= Git
TBD
@@ -0,0 +1,7 @@
= GPG
TBD
== Committers
Please make sure to provide your GPG fingerprint in your Apache committer profile at https://id.apache.org.
@@ -0,0 +1,3 @@
= Gradle
TBD
@@ -0,0 +1,3 @@
= Eclipse
TBD
@@ -0,0 +1,3 @@
= IntelliJ
TBD
@@ -0,0 +1,3 @@
= VSCode
TBD
@@ -0,0 +1,9 @@
= IDE
TBD
include::ide-intellij.adoc[leveloffset=+1]
include::ide-eclipse.adoc[leveloffset=+1]
include::ide-vscode.adoc[leveloffset=+1]
@@ -0,0 +1,17 @@
= Fineract Development Environment
TBD
include::git.adoc[leveloffset=+1]
include::gpg.adoc[leveloffset=+1]
include::docker.adoc[leveloffset=+1]
include::gradle.adoc[leveloffset=+1]
include::ide.adoc[leveloffset=+1]
include::kubernetes.adoc[leveloffset=+1]
include::tools.adoc[leveloffset=+1]
@@ -0,0 +1,3 @@
= Helm Charts
TBD
@@ -0,0 +1,3 @@
= K3d
TBD
@@ -0,0 +1,3 @@
= Microk8s
TBD
@@ -0,0 +1,3 @@
= Minikube
TBD
@@ -0,0 +1,11 @@
= Kubernetes
TBD
include::kubernetes-minikube.adoc[leveloffset=+1]
include::kubernetes-microk8s.adoc[leveloffset=+1]
include::kubernetes-k3d.adoc[leveloffset=+1]
include::kubernetes-helm.adoc[leveloffset=+1]
@@ -0,0 +1,9 @@
= Brew
== MacOS
TBD
== Linux
TBD
@@ -0,0 +1,10 @@
= SDKMAN
We recommend using SKDMAN to manage the following developer tools:
- JDK
- Spring Boot CLI
- Gradle (if you need a global installation)
- AsciidoctorJ
TBD
@@ -0,0 +1,7 @@
= Tools
TBD
include::tools-sdkman.adoc[leveloffset=+1]
include::tools-brew.adoc[leveloffset=+1]
@@ -0,0 +1,5 @@
= Antora
See: https://antora.org/
TBD
@@ -0,0 +1,3 @@
= Cheatsheet
TBD
@@ -0,0 +1,5 @@
= Tutorial
See: https://www.vogella.com/tutorials/AsciiDoc/article.html
TBD

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