From ba765f9b7a398306959aae00571dba6c041ce7c7 Mon Sep 17 00:00:00 2001 From: Aleksandar Vidakovic Date: Mon, 10 Oct 2022 04:54:50 +0200 Subject: [PATCH] FINERACT-1758: Improve custom module example --- build.gradle | 80 +++++- .../acme/loan/cob}/build.gradle | 6 +- .../acme/loan/cob}/dependencies.gradle | 2 +- .../loan/cob/AcmeNoopBusinessStep.java | 56 ++++ .../loan/starter}/build.gradle | 6 +- .../acme/loan/starter}/dependencies.gradle | 8 +- .../starter/AcmeLoanAutoConfiguration.java | 13 +- .../main/resources/META-INF/spring.factories | 2 +- .../starter/AcmeBusinessStepDefinitions.java | 69 +++++ .../starter/TestDefaultConfiguration.java | 51 ++++ .../acme/fineract/loan/starter/TestSuite.java | 15 +- .../resources/application-test.properties | 13 +- .../src/test/resources/cucumber.properties | 21 ++ .../src/test/resources/features/cob.feature | 30 ++ .../starter/src/test/resources/logback.xml | 41 +++ .../acme/note/service}/build.gradle | 6 +- custom/acme/note/service/dependencies.gradle | 23 ++ .../service/AcmeNoteReadPlatformService.java | 14 +- .../service/AcmeNoteWritePlatformService.java | 14 +- .../acme/note/starter}/build.gradle | 6 +- .../note/starter}/dependencies.gradle | 8 +- .../starter/AcmeNoteAutoConfiguration.java | 24 +- .../main/resources/META-INF/spring.factories | 2 + .../custom-changelog/changelog-acme-note.xml | 26 ++ .../parts/0001_acme_note_initial.xml | 36 +++ .../AcmeNoteServiceStepDefinitions.java | 7 +- .../starter}/TestDefaultConfiguration.java | 2 +- .../starter}/TestOverrideConfiguration.java | 4 +- .../portfolio/note/starter/TestSuite.java | 19 +- .../resources/application-test.properties | 20 ++ .../src/test/resources/cucumber.properties | 21 ++ .../src/test/resources/features/note.feature | 12 +- .../starter/src/test/resources/logback.xml | 41 +++ custom/docker/build.gradle | 108 +++++++ custom/docker/docker-compose.yml | 88 ++++++ custom/foo/build.gradle | 19 -- .../foo/service/FooDummyServiceImpl.java | 32 --- .../en/chapters/architecture/modules.adoc | 264 ++++-------------- .../custom-db-migration-folder-structure.puml | 25 ++ .../en/diagrams/custom-folder-structure.puml | 43 ++- .../en/diagrams/modules-folder-structure.puml | 39 --- fineract-provider/dependencies.gradle | 6 +- .../ShareProductDividendAssembler.java | 2 +- .../db/changelog/db.changelog-master.xml | 1 + .../example/DummyServiceStepDefinitions.java | 54 ---- .../example/TestDefaultConfiguration.java | 25 -- module/build.gradle | 19 -- module/dummy/build.gradle | 19 -- module/dummy/starter/dependencies.gradle | 25 -- .../dummy/starter/DummyAutoConfiguration.java | 35 --- settings.gradle | 20 +- 51 files changed, 938 insertions(+), 584 deletions(-) rename {module/dummy/core => custom/acme/loan/cob}/build.gradle (87%) rename {module/dummy/core => custom/acme/loan/cob}/dependencies.gradle (93%) create mode 100644 custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java rename custom/{foo/service => acme/loan/starter}/build.gradle (86%) rename {module/dummy/service => custom/acme/loan/starter}/dependencies.gradle (71%) rename fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java => custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java (71%) rename {module/dummy => custom/acme/loan}/starter/src/main/resources/META-INF/spring.factories (53%) create mode 100644 custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java create mode 100644 custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java rename module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java => custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java (61%) rename fineract-provider/src/test/resources/features/module/modules.example.feature => custom/acme/loan/starter/src/test/resources/application-test.properties (57%) create mode 100644 custom/acme/loan/starter/src/test/resources/cucumber.properties create mode 100644 custom/acme/loan/starter/src/test/resources/features/cob.feature create mode 100644 custom/acme/loan/starter/src/test/resources/logback.xml rename {module/dummy/starter => custom/acme/note/service}/build.gradle (85%) create mode 100644 custom/acme/note/service/dependencies.gradle rename fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java => custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java (71%) rename fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java => custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java (75%) rename {module/dummy/service => custom/acme/note/starter}/build.gradle (85%) rename custom/{foo/service => acme/note/starter}/dependencies.gradle (71%) rename module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java => custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java (66%) create mode 100644 custom/acme/note/starter/src/main/resources/META-INF/spring.factories create mode 100644 custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml create mode 100644 custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml rename fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java => custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java (90%) rename {fineract-provider/src/test/java/org/apache/fineract/module/service => custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter}/TestDefaultConfiguration.java (98%) rename {fineract-provider/src/test/java/org/apache/fineract/module/service => custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter}/TestOverrideConfiguration.java (96%) rename module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java => custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java (60%) create mode 100644 custom/acme/note/starter/src/test/resources/application-test.properties create mode 100644 custom/acme/note/starter/src/test/resources/cucumber.properties rename fineract-provider/src/test/resources/features/module/modules.service.feature => custom/acme/note/starter/src/test/resources/features/note.feature (56%) create mode 100644 custom/acme/note/starter/src/test/resources/logback.xml create mode 100644 custom/docker/build.gradle create mode 100644 custom/docker/docker-compose.yml delete mode 100644 custom/foo/build.gradle delete mode 100644 custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java create mode 100644 fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml delete mode 100644 fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml delete mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java delete mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java delete mode 100644 module/build.gradle delete mode 100644 module/dummy/build.gradle delete mode 100644 module/dummy/starter/dependencies.gradle delete mode 100644 module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java diff --git a/build.gradle b/build.gradle index af38e01e2..1c7e19d3d 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ buildscript { retrofitVersion = '2.9.0' okhttpVersion = '4.9.3' oltuVersion = '1.0.1' + fineractCustomProjects = [] fineractJavaProjects = subprojects.findAll{ [ 'fineract-api', @@ -31,9 +32,6 @@ buildscript { 'twofactor-tests', 'oauth2-tests', 'fineract-client', - 'core', - 'service', - 'starter', 'fineract-avro-schemas' ].contains(it.name) } @@ -257,6 +255,22 @@ allprojects { } } +// NOTE: dynamically load all custom modules +file("${rootDir}/custom").eachDir { companyDir -> + if('build' != companyDir.name && 'docker' != companyDir.name) { + file("${rootDir}/custom/${companyDir.name}").eachDir { categoryDir -> + if('build' != categoryDir.name) { + file("${rootDir}/custom/${companyDir.name}/${categoryDir.name}").eachDir { moduleDir -> + if('build' != moduleDir.name) { + project.fineractJavaProjects.add(project(":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}")) + project.fineractCustomProjects.add(project(":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}")) + } + } + } + } + } +} + configure(project.fineractJavaProjects) { // NOTE: order matters! @@ -522,6 +536,9 @@ configure(project.fineractJavaProjects) { exclude(module: "slf4j-api") } + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation( 'org.mockito:mockito-core', 'org.mockito:mockito-junit-jupiter', 'org.junit.jupiter:junit-jupiter-api', @@ -596,6 +613,63 @@ configure(project.fineractJavaProjects) { } } +configure(project.fineractCustomProjects) { + compileJava { + dependsOn = [] + } + + dependencies { + implementation ( + 'org.springframework:spring-context', + 'ch.qos.logback:logback-core', + 'org.slf4j:slf4j-api', + 'org.slf4j:log4j-over-slf4j', + 'org.slf4j:jul-to-slf4j', + ) + implementation ("ch.qos.logback:logback-classic") { + exclude(module: "slf4j-api") + } + + compileOnly('org.projectlombok:lombok') + annotationProcessor('org.projectlombok:lombok') + annotationProcessor('org.mapstruct:mapstruct-processor') + annotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor') + annotationProcessor('org.springframework.boot:spring-boot-configuration-processor') + + testImplementation ('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'com.jayway.jsonpath', module: 'json-path' + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + exclude group: 'jakarta.activation' + exclude group: 'javax.activation' + exclude group: 'org.skyscreamer' + } + testImplementation( 'org.mockito:mockito-core', + 'org.mockito:mockito-inline', + 'org.mockito:mockito-junit-jupiter', + 'org.junit.jupiter:junit-jupiter-api', + 'org.junit.jupiter:junit-jupiter-engine', + 'org.junit.platform:junit-platform-runner', // required to be able to run tests directly under Eclipse, see FINERACT-943 & FINERACT-1021 + 'org.bouncycastle:bcpkix-jdk15to18', + 'org.bouncycastle:bcprov-jdk15to18', + 'org.awaitility:awaitility', + 'com.google.truth:truth', + 'com.google.truth.extensions:truth-java8-extension', + 'io.github.classgraph:classgraph', + 'io.cucumber:cucumber-core', + 'io.cucumber:cucumber-java', + 'io.cucumber:cucumber-java8', + 'io.cucumber:cucumber-junit-platform-engine', + 'io.cucumber:cucumber-spring', + ) + + testCompileOnly('org.projectlombok:lombok') + testAnnotationProcessor('org.projectlombok:lombok') + testAnnotationProcessor('org.mapstruct:mapstruct-processor') + testAnnotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor') + testAnnotationProcessor('org.springframework.boot:spring-boot-configuration-processor') + } +} + configure(project.fineractPublishProjects) { apply plugin: 'maven-publish' apply plugin: 'signing' diff --git a/module/dummy/core/build.gradle b/custom/acme/loan/cob/build.gradle similarity index 87% rename from module/dummy/core/build.gradle rename to custom/acme/loan/cob/build.gradle index bd83b90f7..104d04edc 100644 --- a/module/dummy/core/build.gradle +++ b/custom/acme/loan/cob/build.gradle @@ -16,8 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -description = 'Fineract Module: Dummy Core' +description = 'ACME Corp.: Fineract COB Loan' -group = 'org.apache.fineract.dummy.core' +group = 'com.acme.fineract.cob' + +archivesBaseName = 'acme-fineract-cob-loan' apply from: 'dependencies.gradle' diff --git a/module/dummy/core/dependencies.gradle b/custom/acme/loan/cob/dependencies.gradle similarity index 93% rename from module/dummy/core/dependencies.gradle rename to custom/acme/loan/cob/dependencies.gradle index e819cc75b..59d668289 100644 --- a/module/dummy/core/dependencies.gradle +++ b/custom/acme/loan/cob/dependencies.gradle @@ -18,5 +18,5 @@ */ dependencies { - implementation('org.springframework:spring-context') + implementation(project(':fineract-provider')) } diff --git a/custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java b/custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java new file mode 100644 index 000000000..85d271d51 --- /dev/null +++ b/custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.acme.fineract.loan.cob; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.loan.LoanCOBBusinessStep; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AcmeNoopBusinessStep implements LoanCOBBusinessStep, InitializingBean { + + // NOTE: just to demonstrate that dependency injection is working + private final LoanAccountDomainService loanAccountDomainService; + + @Override + public void afterPropertiesSet() throws Exception { + log.warn("Acme COB Loan: '{}'", getClass().getCanonicalName()); + } + + @Override + public Loan execute(Loan input) { + return input; + } + + @Override + public String getEnumStyledName() { + return "ACME_LOAN_NOOP"; + } + + @Override + public String getHumanReadableName() { + return "ACME Loan Noop"; + } +} diff --git a/custom/foo/service/build.gradle b/custom/acme/loan/starter/build.gradle similarity index 86% rename from custom/foo/service/build.gradle rename to custom/acme/loan/starter/build.gradle index 8a0f207ee..31e39823f 100644 --- a/custom/foo/service/build.gradle +++ b/custom/acme/loan/starter/build.gradle @@ -16,8 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -description = 'ACME Corp.: Fineract Foo Service' +description = 'ACME Corp.: Fineract COB Starter' -group = 'com.acmecorp.fineract.foo.service' +group = 'com.acme.fineract.cob' + +archivesBaseName = 'acme-fineract-cob-starter' apply from: 'dependencies.gradle' diff --git a/module/dummy/service/dependencies.gradle b/custom/acme/loan/starter/dependencies.gradle similarity index 71% rename from module/dummy/service/dependencies.gradle rename to custom/acme/loan/starter/dependencies.gradle index 17ee19c84..6276b6322 100644 --- a/module/dummy/service/dependencies.gradle +++ b/custom/acme/loan/starter/dependencies.gradle @@ -18,7 +18,9 @@ */ dependencies { - implementation(project(':module:dummy:core'), - 'org.springframework:spring-context') - testImplementation('io.cucumber:cucumber-spring') + implementation(project(':custom:acme:loan:cob')) + implementation('org.springframework.boot:spring-boot-starter') + testImplementation(project(':fineract-provider')) + testImplementation('org.springframework.boot:spring-boot-starter-jdbc') + testImplementation('org.springframework.boot:spring-boot-starter-data-jpa') } diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java b/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java similarity index 71% rename from fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java rename to custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java index c22bf6313..91dc820c0 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java +++ b/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.module.example; +package com.acme.fineract.loan.starter; -import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.ComponentScan; -@EnableConfigurationProperties({ FineractProperties.class }) -@ComponentScan("com.acmecorp.fineract.foo") -public class TestFooConfiguration {} +@AutoConfiguration +@ComponentScan("com.acme.fineract.loan.cob") +@ConditionalOnProperty("acme.loan.enabled") +public class AcmeLoanAutoConfiguration {} diff --git a/module/dummy/starter/src/main/resources/META-INF/spring.factories b/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories similarity index 53% rename from module/dummy/starter/src/main/resources/META-INF/spring.factories rename to custom/acme/loan/starter/src/main/resources/META-INF/spring.factories index 5dcbd5695..a3c5b7e58 100644 --- a/module/dummy/starter/src/main/resources/META-INF/spring.factories +++ b/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories @@ -1,2 +1,2 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.apache.fineract.dummy.starter.DummyAutoConfiguration \ No newline at end of file +com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration \ No newline at end of file diff --git a/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java new file mode 100644 index 000000000..9e6469bc9 --- /dev/null +++ b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java @@ -0,0 +1,69 @@ +/** + * 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 com.acme.fineract.loan.starter; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.java8.En; +import java.util.TreeMap; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBBusinessStep; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +@Slf4j +public class AcmeBusinessStepDefinitions implements En { + + private ApplicationContextRunner contextRunner; + + private COBBusinessStepService businessStepService; + + private COBBusinessStep businessStep; + + private TreeMap result; + + public AcmeBusinessStepDefinitions() { + Given("/^An auto configuration (.*) and a service configuration (.*)$/", + (String autoConfigurationClassName, String configurationClassName) -> { + contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName))) + .withPropertyValues("acme.loan.enabled", "true") + .withUserConfiguration(Class.forName(configurationClassName.trim())); + }); + + When("/^The user retrieves the step service with step class (.*) and name (.*)$/", (String stepClass, String stepName) -> { + contextRunner.run((ctx) -> { + this.businessStepService = ctx.getBean(COBBusinessStepService.class); + + this.businessStep = ctx.getBean((Class>) Class.forName(stepClass)); + + // TODO: not yet working, because no storage configured/mocked + this.result = businessStepService.getCOBBusinessStepMap(this.businessStep.getClass(), stepName); + }); + }); + + Then("/^The step service should have a result$/", () -> { + assertThat(this.businessStep).isNotNull(); + assertThat(this.result).isNotNull(); + // log.warn(">>>>>>>>>>>>>>>>>> RESULT: {}", this.result); + }); + } +} diff --git a/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java new file mode 100644 index 000000000..39e463ff3 --- /dev/null +++ b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.acme.fineract.loan.starter; + +import static org.mockito.Mockito.mock; + +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.COBBusinessStepServiceImpl; +import org.apache.fineract.cob.domain.BatchBusinessStepRepository; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +@EnableConfigurationProperties({ FineractProperties.class }) +public class TestDefaultConfiguration { + + @Bean + public BatchBusinessStepRepository batchBusinessStepRepository() { + return mock(BatchBusinessStepRepository.class); + } + + @Bean + public COBBusinessStepService cobBusinessStepService(BatchBusinessStepRepository batchBusinessStepRepository, + ApplicationContext context, ListableBeanFactory beanFactory) { + return new COBBusinessStepServiceImpl(batchBusinessStepRepository, context, beanFactory); + } + + @Bean + public LoanAccountDomainService loanAccountDomainService() { + return mock(LoanAccountDomainService.class); + } +} diff --git a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java similarity index 61% rename from module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java rename to custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java index fdcd4a17e..cd1c32d3c 100644 --- a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java +++ b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java @@ -16,11 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.dummy.core.service; +package com.acme.fineract.loan.starter; -import org.apache.fineract.dummy.core.data.DummyMessage; +import io.cucumber.spring.CucumberContextConfiguration; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; -public interface DummyService { +@CucumberContextConfiguration +@ExtendWith(SpringExtension.class) +@TestPropertySource("classpath:application-test.properties") +@WebAppConfiguration +public class TestSuite { - DummyMessage getMessage(); } diff --git a/fineract-provider/src/test/resources/features/module/modules.example.feature b/custom/acme/loan/starter/src/test/resources/application-test.properties similarity index 57% rename from fineract-provider/src/test/resources/features/module/modules.example.feature rename to custom/acme/loan/starter/src/test/resources/application-test.properties index c4454a4f1..0a2848936 100644 --- a/fineract-provider/src/test/resources/features/module/modules.example.feature +++ b/custom/acme/loan/starter/src/test/resources/application-test.properties @@ -17,15 +17,4 @@ # under the License. # -Feature: Example Modules - - @modules - Scenario Outline: Verify that the dummy service returns the correct message - Given A dummy service configuration - When The user gets the dummy service message - Then The dummy service message should match - - Examples: - | configurationClass | message | - | org.apache.fineract.module.example.TestDefaultConfiguration | Hello: DEFAULT DUMMY! | - | org.apache.fineract.module.example.TestFooConfiguration | Hello: FOO! | \ No newline at end of file +# test properties diff --git a/custom/acme/loan/starter/src/test/resources/cucumber.properties b/custom/acme/loan/starter/src/test/resources/cucumber.properties new file mode 100644 index 000000000..b3ee387c2 --- /dev/null +++ b/custom/acme/loan/starter/src/test/resources/cucumber.properties @@ -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. +# + +cucumber.publish.quiet=true +cucumber.glue=com.acme.fineract diff --git a/custom/acme/loan/starter/src/test/resources/features/cob.feature b/custom/acme/loan/starter/src/test/resources/features/cob.feature new file mode 100644 index 000000000..871728f85 --- /dev/null +++ b/custom/acme/loan/starter/src/test/resources/features/cob.feature @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +Feature: COB Feature + + @modules + Scenario Outline: Verify that the dependency injection of steps into the step service works + Given An auto configuration and a service configuration + When The user retrieves the step service with step class and name + Then The step service should have a result + + Examples: + | autoConfigurationClass | configurationClass | stepClass | stepName | + | com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration | com.acme.fineract.loan.starter.TestDefaultConfiguration | com.acme.fineract.loan.cob.AcmeNoopBusinessStep | ACME_LOAN_NOOP | diff --git a/custom/acme/loan/starter/src/test/resources/logback.xml b/custom/acme/loan/starter/src/test/resources/logback.xml new file mode 100644 index 000000000..30cf96640 --- /dev/null +++ b/custom/acme/loan/starter/src/test/resources/logback.xml @@ -0,0 +1,41 @@ + + + + + false + + + + true + + %green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n + + + + + + + + + + + diff --git a/module/dummy/starter/build.gradle b/custom/acme/note/service/build.gradle similarity index 85% rename from module/dummy/starter/build.gradle rename to custom/acme/note/service/build.gradle index ab64be956..90fdb314f 100644 --- a/module/dummy/starter/build.gradle +++ b/custom/acme/note/service/build.gradle @@ -16,8 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -description = 'Fineract Module: Dummy Starter' +description = 'ACME Corp.: Fineract Note Service' -group = 'org.apache.fineract.dummy.starter' +group = 'com.acme.fineract.portfolio.note' + +archivesBaseName = 'acme-fineract-note-service' apply from: 'dependencies.gradle' diff --git a/custom/acme/note/service/dependencies.gradle b/custom/acme/note/service/dependencies.gradle new file mode 100644 index 000000000..a81ef21a1 --- /dev/null +++ b/custom/acme/note/service/dependencies.gradle @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +dependencies { + implementation(project(':fineract-provider')) + compileOnly('org.springframework.boot:spring-boot-autoconfigure') +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java similarity index 71% rename from fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java rename to custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java index 7d4e5c0b0..3898ba030 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java +++ b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java @@ -16,15 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.module.service.custom; +package com.acme.fineract.portfolio.note.service; import java.util.Collection; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.portfolio.note.data.NoteData; import org.apache.fineract.portfolio.note.service.NoteReadPlatformService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +@Slf4j @Service -public class CustomNoteReadPlatformService implements NoteReadPlatformService { +@ConditionalOnProperty("acme.note.enabled") +public class AcmeNoteReadPlatformService implements NoteReadPlatformService, InitializingBean { + + @Override + public void afterPropertiesSet() throws Exception { + log.warn("Note Read Service: '{}'", getClass().getCanonicalName()); + } @Override public NoteData retrieveNote(Long noteId, Long resourceId, Integer noteTypeId) { diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java similarity index 75% rename from fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java rename to custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java index ecc82db90..1fb0572a8 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java +++ b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java @@ -16,16 +16,26 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.module.service.custom; +package com.acme.fineract.portfolio.note.service; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.note.service.NoteWritePlatformService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +@Slf4j @Service -public class CustomNoteWritePlatformService implements NoteWritePlatformService { +@ConditionalOnProperty("acme.note.enabled") +public class AcmeNoteWritePlatformService implements NoteWritePlatformService, InitializingBean { + + @Override + public void afterPropertiesSet() throws Exception { + log.warn("Note Write Service: '{}'", getClass().getCanonicalName()); + } @Override public CommandProcessingResult createNote(JsonCommand command) { diff --git a/module/dummy/service/build.gradle b/custom/acme/note/starter/build.gradle similarity index 85% rename from module/dummy/service/build.gradle rename to custom/acme/note/starter/build.gradle index 8e59db880..49bf235b5 100644 --- a/module/dummy/service/build.gradle +++ b/custom/acme/note/starter/build.gradle @@ -16,8 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -description = 'Fineract Module: Dummy Service' +description = 'ACME Corp.: Fineract Note Starter' -group = 'org.apache.fineract.dummy.service' +group = 'com.acme.fineract.portfolio.note' + +archivesBaseName = 'acme-fineract-note-starter' apply from: 'dependencies.gradle' diff --git a/custom/foo/service/dependencies.gradle b/custom/acme/note/starter/dependencies.gradle similarity index 71% rename from custom/foo/service/dependencies.gradle rename to custom/acme/note/starter/dependencies.gradle index 17ee19c84..3fd62233d 100644 --- a/custom/foo/service/dependencies.gradle +++ b/custom/acme/note/starter/dependencies.gradle @@ -18,7 +18,9 @@ */ dependencies { - implementation(project(':module:dummy:core'), - 'org.springframework:spring-context') - testImplementation('io.cucumber:cucumber-spring') + implementation(project(':custom:acme:note:service')) + implementation('org.springframework.boot:spring-boot-starter') + testImplementation(project(':fineract-provider')) + testImplementation('org.springframework.boot:spring-boot-starter-jdbc') + testImplementation('org.springframework.boot:spring-boot-starter-data-jpa') } diff --git a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java b/custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java similarity index 66% rename from module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java rename to custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java index c6e35f712..60629ef91 100644 --- a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java +++ b/custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java @@ -16,21 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.dummy.core.data; +package com.acme.fineract.portfolio.note.starter; -public class DummyMessage { +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.ComponentScan; - private String message; - - public DummyMessage(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } -} +@AutoConfiguration +@ComponentScan("com.acme.fineract.portfolio.note") +@ConditionalOnProperty("acme.note.enabled") +public class AcmeNoteAutoConfiguration {} diff --git a/custom/acme/note/starter/src/main/resources/META-INF/spring.factories b/custom/acme/note/starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..d3f0e811b --- /dev/null +++ b/custom/acme/note/starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration \ No newline at end of file diff --git a/custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml b/custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml new file mode 100644 index 000000000..601ba8f08 --- /dev/null +++ b/custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml b/custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml new file mode 100644 index 000000000..e7fa227b3 --- /dev/null +++ b/custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java similarity index 90% rename from fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java rename to custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java index 175909734..5807e0b80 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java +++ b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.module.service; +package com.acme.fineract.portfolio.note.starter; import static org.assertj.core.api.Assertions.assertThat; @@ -24,7 +24,7 @@ import io.cucumber.java8.En; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -public class ServiceStepDefinitions implements En { +public class AcmeNoteServiceStepDefinitions implements En { private Class interfaceClass; @@ -32,11 +32,12 @@ public class ServiceStepDefinitions implements En { private ApplicationContextRunner contextRunner; - public ServiceStepDefinitions() { + public AcmeNoteServiceStepDefinitions() { Given("/^An auto configuration (.*) and a service configuration (.*)$/", (String autoConfigurationClassName, String configurationClassName) -> { contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName))) + .withPropertyValues("acme.note.enabled", "true") .withUserConfiguration(Class.forName(configurationClassName.trim())); }); diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java similarity index 98% rename from fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java rename to custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java index ffe76f46b..da74531f2 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java +++ b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.module.service; +package com.acme.fineract.portfolio.note.starter; import static org.mockito.Mockito.mock; diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java similarity index 96% rename from fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java rename to custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java index 6abb1ad04..173671a0b 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java +++ b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.module.service; +package com.acme.fineract.portfolio.note.starter; import static org.mockito.Mockito.mock; @@ -33,7 +33,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.jdbc.core.JdbcTemplate; -@ComponentScan("org.apache.fineract.module.service.custom") +@ComponentScan("com.acme.fineract") public class TestOverrideConfiguration { // NOTE: unfortunately an abastract base class that contains all these mock functions won't work diff --git a/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java similarity index 60% rename from module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java rename to custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java index 5445c8b83..71bd92b50 100644 --- a/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java +++ b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java @@ -16,15 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.dummy.service; +package com.acme.fineract.portfolio.note.starter; -import org.apache.fineract.dummy.core.data.DummyMessage; -import org.apache.fineract.dummy.core.service.DummyService; +import io.cucumber.spring.CucumberContextConfiguration; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; -public class DummyServiceImpl implements DummyService { +@CucumberContextConfiguration +@ExtendWith(SpringExtension.class) +@TestPropertySource("classpath:application-test.properties") +@WebAppConfiguration +public class TestSuite { - @Override - public DummyMessage getMessage() { - return new DummyMessage("Hello: DEFAULT DUMMY!"); - } } diff --git a/custom/acme/note/starter/src/test/resources/application-test.properties b/custom/acme/note/starter/src/test/resources/application-test.properties new file mode 100644 index 000000000..0a2848936 --- /dev/null +++ b/custom/acme/note/starter/src/test/resources/application-test.properties @@ -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. +# + +# test properties diff --git a/custom/acme/note/starter/src/test/resources/cucumber.properties b/custom/acme/note/starter/src/test/resources/cucumber.properties new file mode 100644 index 000000000..b3ee387c2 --- /dev/null +++ b/custom/acme/note/starter/src/test/resources/cucumber.properties @@ -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. +# + +cucumber.publish.quiet=true +cucumber.glue=com.acme.fineract diff --git a/fineract-provider/src/test/resources/features/module/modules.service.feature b/custom/acme/note/starter/src/test/resources/features/note.feature similarity index 56% rename from fineract-provider/src/test/resources/features/module/modules.service.feature rename to custom/acme/note/starter/src/test/resources/features/note.feature index e2cc191a4..5895c75c8 100644 --- a/fineract-provider/src/test/resources/features/module/modules.service.feature +++ b/custom/acme/note/starter/src/test/resources/features/note.feature @@ -17,7 +17,7 @@ # under the License. # -Feature: Service Modules +Feature: Note Feature @modules Scenario Outline: Verify that the dummy service returns the correct message @@ -26,8 +26,8 @@ Feature: Service Modules Then The service class should match Examples: - | autoConfigurationClass | configurationClass | interfaceClass | serviceClass | - | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | org.apache.fineract.module.service.TestDefaultConfiguration | org.apache.fineract.portfolio.note.service.NoteReadPlatformService | org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl | - | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | org.apache.fineract.module.service.TestDefaultConfiguration | org.apache.fineract.portfolio.note.service.NoteWritePlatformService | org.apache.fineract.portfolio.note.service.NoteWritePlatformServiceJpaRepositoryImpl | - | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | org.apache.fineract.module.service.TestOverrideConfiguration | org.apache.fineract.portfolio.note.service.NoteReadPlatformService | org.apache.fineract.module.service.custom.CustomNoteReadPlatformService | - | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | org.apache.fineract.module.service.TestOverrideConfiguration | org.apache.fineract.portfolio.note.service.NoteWritePlatformService | org.apache.fineract.module.service.custom.CustomNoteWritePlatformService | \ No newline at end of file + | autoConfigurationClass | configurationClass | interfaceClass | serviceClass | + | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | com.acme.fineract.portfolio.note.starter.TestDefaultConfiguration | org.apache.fineract.portfolio.note.service.NoteReadPlatformService | org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl | + | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | com.acme.fineract.portfolio.note.starter.TestDefaultConfiguration | org.apache.fineract.portfolio.note.service.NoteWritePlatformService | org.apache.fineract.portfolio.note.service.NoteWritePlatformServiceJpaRepositoryImpl | + | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | com.acme.fineract.portfolio.note.starter.TestOverrideConfiguration | org.apache.fineract.portfolio.note.service.NoteReadPlatformService | com.acme.fineract.portfolio.note.service.AcmeNoteReadPlatformService | + | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | com.acme.fineract.portfolio.note.starter.TestOverrideConfiguration | org.apache.fineract.portfolio.note.service.NoteWritePlatformService | com.acme.fineract.portfolio.note.service.AcmeNoteWritePlatformService | \ No newline at end of file diff --git a/custom/acme/note/starter/src/test/resources/logback.xml b/custom/acme/note/starter/src/test/resources/logback.xml new file mode 100644 index 000000000..30cf96640 --- /dev/null +++ b/custom/acme/note/starter/src/test/resources/logback.xml @@ -0,0 +1,41 @@ + + + + + false + + + + true + + %green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n + + + + + + + + + + + diff --git a/custom/docker/build.gradle b/custom/docker/build.gradle new file mode 100644 index 000000000..8d1a91711 --- /dev/null +++ b/custom/docker/build.gradle @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +description = 'Fineract Custom Docker Image' + +apply plugin: 'java' +apply plugin: 'com.google.cloud.tools.jib' +apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle" + +jib { + from { + image = 'azul/zulu-openjdk-alpine:17' + } + + to { + image = 'fineract-custom' + tags = [ + "${project.version}", + 'latest' + ] + } + + container { + creationTime = 'USE_CURRENT_TIMESTAMP' + mainClass = 'org.apache.fineract.ServerApplication' + jvmFlags = [ + '-Xms1G', + '-XshowSettings:vm', + '-XX:+UseContainerSupport', + '-XX:+UseStringDeduplication', + '-XX:MinRAMPercentage=25', + '-XX:MaxRAMPercentage=80', + '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED', + '--add-opens=java.base/java.lang=ALL-UNNAMED', + '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED', + '--add-opens=java.base/java.io=ALL-UNNAMED', + '--add-opens=java.base/java.security=ALL-UNNAMED', + '--add-opens=java.base/java.util=ALL-UNNAMED', + '--add-opens=java.management/javax.management=ALL-UNNAMED', + '--add-opens=java.naming/javax.naming=ALL-UNNAMED' + ] + args = [ + '-Duser.home=/tmp', + '-Dfile.encoding=UTF-8', + '-Duser.timezone=UTC', + '-Djava.security.egd=file:/dev/./urandom' + ] + ports = ['8080/tcp', '8443/tcp'] + labels = [maintainer: 'Aleksandar Vidakovic '] + user = 'nobody:nogroup' + } + + allowInsecureRegistries = true + + dependencies { + implementation project(':fineract-provider') + // NOTE: dynamically load all custom modules + file("${rootDir}/custom").eachDir { companyDir -> + if('build' != companyDir.name && 'docker' != companyDir.name) { + file("${rootDir}/custom/${companyDir.name}").eachDir { categoryDir -> + if('build' != categoryDir.name) { + file("${rootDir}/custom/${companyDir.name}/${categoryDir.name}").eachDir { moduleDir -> + if('build' != moduleDir.name) { + implementation project(":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}") + } + } + } + } + } + } + // NOTE: other custom dependencies + implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.7' + implementation 'mysql:mysql-connector-java:8.0.30' + implementation 'org.postgresql:postgresql:42.5.0' + annotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor') + annotationProcessor('org.springframework.boot:spring-boot-configuration-processor') + } + + pluginExtensions { + pluginExtension { + implementation = 'com.google.cloud.tools.jib.gradle.extension.layerfilter.JibLayerFilterExtension' + configuration { + filters { + filter { + glob = '/app/resources/**' + } + } + } + } + } +} + +// tasks.jibDockerBuild.dependsOn(':fineract-provider:bootJar') diff --git a/custom/docker/docker-compose.yml b/custom/docker/docker-compose.yml new file mode 100644 index 000000000..29814498e --- /dev/null +++ b/custom/docker/docker-compose.yml @@ -0,0 +1,88 @@ +# 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. +# + +# NB: Travis CI (dist: bionic) supports up to v3.7 +# (unless we'd install a more recent version in .travis.yml) +version: '3.7' +services: + # Backend service + fineractmysql: + image: mariadb:10.9 + volumes: + - ../../fineract-db/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf + - ../../fineract-db/docker:/docker-entrypoint-initdb.d:Z,ro + restart: always + environment: + MYSQL_ROOT_PASSWORD: skdcnwauicn2ucnaecasdsajdnizucawencascdca + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--password=skdcnwauicn2ucnaecasdsajdnizucawencascdca" ] + timeout: 10s + retries: 10 + ports: + - "3306:3306" + fineract-server: + image: fineract-custom:latest + volumes: + - ../../fineract-provider/build/data:/data + healthcheck: + test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "fineract-server" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ] + timeout: 10s + retries: 10 + ports: + - 8443:8443 + depends_on: + fineractmysql: + condition: service_healthy + environment: + # NOTE: node aware scheduler + - FINERACT_NODE_ID=1 + # NOTE: env vars prefixed "FINERACT_HIKARI_*" are used to configure the database connection pool + - FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME=org.mariadb.jdbc.Driver + - FINERACT_HIKARI_JDBC_URL=jdbc:mariadb://fineractmysql:3306/fineract_tenants + - FINERACT_HIKARI_USERNAME=root + - FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca + # ... following variables are optional; "application.properties" contains reasonable defaults (same as here) + - FINERACT_HIKARI_MINIMUM_IDLE=3 + - FINERACT_HIKARI_MAXIMUM_POOL_SIZE=10 + - FINERACT_HIKARI_IDLE_TIMEOUT=60000 + - FINERACT_HIKARI_CONNECTION_TIMEOUT=20000 + - FINERACT_HIKARI_TEST_QUERY=SELECT 1 + - FINERACT_HIKARI_AUTO_COMMIT=true + - FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS=true + - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE=250 + - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT=2048 + - FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS=true + - FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE=true + - FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS=true + - FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA=true + - FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION=true + - FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS=true + - FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS=false + - FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES=true + - FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION=true + # NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to create the default tenant database + - FINERACT_DEFAULT_TENANTDB_HOSTNAME=fineractmysql + - FINERACT_DEFAULT_TENANTDB_PORT=3306 + - FINERACT_DEFAULT_TENANTDB_UID=root + - FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca + - FINERACT_DEFAULT_TENANTDB_CONN_PARAMS= + - FINERACT_DEFAULT_TENANTDB_TIMEZONE=Asia/Kolkata + - FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default + - FINERACT_DEFAULT_TENANTDB_NAME=fineract_default + - FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant + - JAVA_TOOL_OPTIONS="-Xmx1G" diff --git a/custom/foo/build.gradle b/custom/foo/build.gradle deleted file mode 100644 index 9b7233870..000000000 --- a/custom/foo/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -description = 'ACME Corp.: Fineract Foo' diff --git a/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java b/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java deleted file mode 100644 index 059224b22..000000000 --- a/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.acmecorp.fineract.foo.service; - -import org.apache.fineract.dummy.core.data.DummyMessage; -import org.apache.fineract.dummy.core.service.DummyService; -import org.springframework.stereotype.Service; - -@Service -public class FooDummyServiceImpl implements DummyService { - - @Override - public DummyMessage getMessage() { - return new DummyMessage("Hello: FOO!"); - } -} diff --git a/fineract-doc/src/docs/en/chapters/architecture/modules.adoc b/fineract-doc/src/docs/en/chapters/architecture/modules.adoc index 9eb720043..50d63367b 100644 --- a/fineract-doc/src/docs/en/chapters/architecture/modules.adoc +++ b/fineract-doc/src/docs/en/chapters/architecture/modules.adoc @@ -1,250 +1,82 @@ = Modules -Currently modules are a proof of concept feature in Fineract. +NOTE: Currently modules are a proof of concept feature in Fineract. -== 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. - -1. Create a folder under `module` and name it _mymodule_. -2. Create a folder named _core_ under `module/mymodule`. -3. Create a folder named _service_ under `module/mymodule`. -4. Create a folder named _starter_ under `module/mymodule`. -5. Setup folders and Gradle build files for a Java project under `core`, `service` and `starter`. -6. Create a text file called _spring.factories in folder `modules/mymodule/starter/src/main/resources/META-INF`. -+ -The final folder and file structure should look somewhat like this: -+ -[plantuml, format=svg] ----- -include::{diagramsdir}/modules-folder-structure.puml[] ----- - -7. Make sure that your new modules are present in settings.gradle: -+ -[source,groovy] ----- -rootProject.name='fineract' -include ':module:mymodule:core' // <.> -include ':module:mymodule:service' -include ':module:mymodule:starter' -include ':fineract-provider' -include ':fineract-war' -include ':integration-tests' -include ':twofactor-tests' -include ':oauth2-tests' -include ':fineract-client' -include ':fineract-doc' ----- -<.> The `settings.gradle` file should contain something like this (and following). - -== How to replace an existing Fineract service +== Custom modules 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. -You can of course choose whatever folder/project structure you like for your custom modules. But we'll describe here some best practices to avoid merge conflicts when fetching updates from Fineract's upstream Git repository. For the time being we suggest to create your custom modules in the same folder as Fineract in a forked Git repo. +The folder structure for modules is based on a convention that ensures that your extensions don't clash with Fineract's internals. This is to make sure that your downstream forks of Fineract are easy to sync. In the past we had all kinds of strategies to add custom code - including editing existing sources in `fineract-provider`. This is not recommended. -As soon as we can publish Fineract module JARs to Maven Central you'll have more freedom to setup your projects (including to setup separate Git repos). +IMPORTANT: At the moment the only service(s) we prepared to be overridden/replaced are `org.apache.fineract.portfolio.note.service.NoteReadPlatformService` and `org.apache.fineract.portfolio.note.service.NoteWritePlatformService`. Please reach out on the developer mailing list if you need other services. + +The recommended folder structure is very simple. If you follow this recommendation you'll get some additional benefits, e. g. you don't even have to edit `settings.gradle` to include your new custom modules. Your modules will also be automatically included in a custom Fineract Docker image build that you can use for your production deployments. + +Let's assume your company/org is called "ACME Inc." and you are trying to (fully/partially) replace an existing Fineract service, let's say those in `org.apache.fineract.portfolio.note`. The recommended folder structure would then look something like this: -1. Create a folder under `custom` and name it _mycustom_. -2. Create a folder named _service_ under `custom/mycustom`. -3. Setup folders and Gradle build files for a Java project under `service`. -4. Create a text file called _spring.factories in folder `modules/mymodule/starter/src/main/resources/META-INF`: -+ -[source,properties] ----- -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.apache.fineract.mymodule.starter.MyAutoConfiguration ----- -+ -The final folder and file structure should look somewhat like this: -+ [plantuml, format=svg] ---- include::{diagramsdir}/custom-folder-structure.puml[] ---- + +As soon as we can publish Fineract module JARs to Maven Central you'll have more freedom to setup your projects (including to setup separate Git repos). But for now please follow these instructions: + +1. Create a folder under `custom` and name it according to your company/organisation (e. g. `acme` if your company is `ACME Inc.`); this way your custom modules can't clash even with other companies' modules +2. Under your company folder create a folder for the `category` or `domain` your module is targeting; e. g. "loan", "client", "account" etc. +3. Finally, setup `library` folders for the actual modules you want to create; usually that will be to replace/extend some existing service, so there could be a `service` folder, maybe even a `core` folder, e. g. if you want to add additional DTOs etc.; we have also an example for COB business steps +4. Per `category`/`domain` you should have a `starter` library; means: a Spring Boot auto-configuration setup that makes including your module in Fineract easier ("hands-free"); the necessary parts for a auto-configuration library are a Spring Java configuration class (annotated with `@Configuration`) and a text file at `META-INF/spring.factories` in your starter resource folder: + -Please make sure that your `service` module's build.gradle file has a unique group: -+ -[source,groovy] ----- -description = 'My Company: Fineract My Custom Service' - -group = 'com.mycompany.fineract.custom.service' // <.> - -apply from: 'dependencies.gradle' ----- -+ -<.> The best choice here is to name it like you would name your JAR artifact on Maven Central. - -5. Make sure that your custom modules are present in settings.gradle: : -+ -[source,groovy] ----- -rootProject.name='fineract' -include ':custom:mycustom:service' // <.> -include ':fineract-provider' -include ':fineract-war' -include ':integration-tests' -include ':twofactor-tests' -include ':oauth2-tests' -include ':fineract-client' -include ':fineract-doc' ----- -<.> The `settings.gradle` file should contain something like this. -6. The dependency.gradle file could look something like this: -[source,groovy] ----- -dependencies { - implementation(project(':module:mymodule:core'), // <.> - 'org.springframework:spring-context') - compileOnly project(':fineract-provider') // <.> -} ----- -<.> If you are replacing a "clean room" module then you just need to include a dependency on the modules `core` library. -<.> If you are replacing a service that is not yet extracted from `fineract-provider` then you need to add a dependency on it (only needed during compilation). -1. When the custom module is built then you can add the JAR in your Fineract's libs folder (or in Tomcat's libs folder if you are deploying as a WAR app). - -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 - -=== Dummy Module Structure - -We've created a demonstration how modules are supposed to be used. A module has usually at least two sub-modules: - -1. a _core_ module that contains mostly Java interfaces of the services and/or other components that we'd like to make replacable. The _core_ module could also contain data (DTO) or domain (entity) classes if necessary. -2. a _service_ module - -IMPORTANT: Replacing parts of the existing REST API and extending it with custom endpoints is at the moment out of scope. Probably we'll need to cleanup and improve the REST layer quite a bit (remove boilerplate code as much as possible, use Jackson for JSON de-/serialization instead of the manual GSON mappers/helper everywhere) before this can happen. For now we'll concentrate primarily on service classes. - -Let's assume we have a service `DummyService` in our system. The service is outlined as a Java interface in module folder `module/dummy/core`. - -[source,java] ----- -include::{rootdir}/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java[lines=19..] ----- - -To make it a little bit more interesting the service's only function returns a simple data object that has one string attribute: - -[source,java] ----- -include::{rootdir}/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java[lines=19..] ----- - -A default implementation of `DummyService` is provided with `DummyServiceImpl`: - -[source,java] ----- -include::{rootdir}/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java[lines=19..] ----- - -As you can see there are no annotations like `@Service` or `@Component`. To have full control over the instantiation and dependency injection we provide a so called _starter_ module. This module just contains one or more (auto-) configuration classes. In this case it's just one configuration class. This is basically a very simple Spring Java configuration class annotated with `@Configuration` containing one method that instantiates our default service implementation (annotated with `@Bean`): - -[source,java] ----- -include::{rootdir}/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java[lines=19..] ----- - -The interesting part is this line: - -[source,java] ----- -include::{rootdir}/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java[lines=28..28] ----- - -This annotation ensures that our default implementation is only instantiated if no other implementation for `DummyService` is provided. There's only one piece missing to make auto configuration work seamlessly and without any explicit configuration in `fineract-provider`: - [source,properties] ---- -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.apache.fineract.dummy.starter.DummyAutoConfiguration +include::{rootdir}/custom/acme/note/starter/src/main/resources/META-INF/spring.factories[] ---- - -This means if we would only include the following dependencies in our main project (e. g. `fineract-provider`)... - ++ +Please make sure that your module libraries have proper `build.gradle` files: ++ [source,groovy] ---- -dependencies { - implementation project(':module:dummy:core') - implementation project(':module:dummy:service') - implementation project(':module:dummy:starter') -} +include::{rootdir}/custom/acme/note/service/build.gradle[lines=19..] ---- - -\... then the message object would contain - -[source,text] ----- -Hello: DEFAULT DUMMY! ----- - -=== 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: - -[source,java] ----- -include::{rootdir}/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java[lines=19..] ----- - -As you can see we have no other sub-modules (e. g. there's no starter module) and this implementation contains the well known `@Service` annotation. If we add this dependency to our main project... - ++ +NOTE: You don't need to edit `settings.gradle` to add your modules/libraries. If you follow above convention they'll get included automatically. ++ +5. The dependency.gradle file could look something like this: [source,groovy] ---- -dependencies { - implementation project(':module:dummy:core') - implementation project(':module:dummy:service') - implementation project(':module:dummy:starter') - implementation project(':custom:foo:service') -} +include::{rootdir}/custom/acme/note/service/dependencies.gradle[lines=19..] ---- -\... then the expected behavior is that we'll get the message +NOTE: We've included by default some basic and useful dependencies for all custom modules, like Slf4j, Lombok, the usual testing frameworks (JUnit, Cucumber, Mockito etc.) -[source,text] +IMPORTANT: Do not include your custom module in `fineract-provider`'s dependency.gradle file. This creates a circular dependency and will fail your build. + +=== Database Migrations + +If database migrations are needed as part of your customizations then you can add your own migration scripts. This is again based on conventions: + +1. Create folders `db/custom-changelog` in one of your `resources` folders; we recommend using the resources folder in your starter library, but actually any of your custom libs will do. +2. Under `db/custom-changelog` create an XML changelog file, e. g. `changelog-acme-note.xml`; you are free to choose a name for this file, but we recommend being consistent to avoid classpath conflicts. +3. Under `db/custom-changelog` create a folder `parts` for your specific changelogs + +[plantuml, format=svg] ---- -Hello: Foo! ----- - -=== 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) - -[source,gherkin] ----- -include::{rootdir}/fineract-provider/src/test/resources/features/module/modules.example.feature[] ----- - -Here's the Spring Java configuration that does *NOT* load the `foo` module: - -[source,java] ----- -include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java[lines=19..] ----- - -\... and here's the configuration that loads the `foo` module (and overrides the deefault implementation) - -[source,java] ----- -include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java[lines=19..] ----- - -The important part in the `foo` configuration is this line: - -[source,java] ----- -include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java[lines=26..26] +include::{diagramsdir}/custom-db-migration-folder-structure.puml[] ---- === 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. +Custom modules (better: the JAR files) only need to be dropped in Fineract's `libs` folder if you run Fineract from the Spring Boot JAR file. Dynamic loading of external JARs is provided since Fineract version 1.5.0. For your convenience we've created a separate Docker image module that automatically includes your custom modules (see `custom/docker`). You can build this Docker image with -=== How can I start replacing services now +[source,bash] +---- +./gradlew :custom:docker:jibDockerBuild +---- + +The Docker image with included custom modules is called `fineract-custom`. + +NOTE: We'll provide soon a way to customize the Docker image parameters (image name, JVM implementation, JVM args, ports etc.). -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 -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. +If this *proof of concept* is accepted we could prepare Fineract's services to be replaceable. This approach works already very well even if we don't have proper JAR libraries published on Maven Central. It's an important goal to separate customized code from Fineract's internals to have soon real modules. diff --git a/fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml b/fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml new file mode 100644 index 000000000..17b2c8cf6 --- /dev/null +++ b/fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml @@ -0,0 +1,25 @@ +@startsalt +{ + {T + + custom + ++ acme + +++ note + ++++ starter + +++++ src + ++++++ main + +++++++ java + ++++++++ com.acme.fineract.note.starter + +++++++++ AcmeNoteAutoConfiguration.java + +++++ resources + ++++++ db + +++++++ custom-changelog + ++++++++ parts + +++++++++ 0001_acme_note_initial.xml + ++++++++ changelog-acme-note.xml + ++++++ META-INF + +++++++ spring.factories + +++++ build.gradle + +++++ dependencies.gradle + } +} +@endsalt \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml b/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml index bc9b3b577..b0a73ae2a 100644 --- a/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml +++ b/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml @@ -2,16 +2,39 @@ { {T + custom - ++ mycustom - +++ service - ++++ src - +++++ main - ++++++ java - +++++++ org.apache.fineract.mymodule.service - ++++++++ MyServiceImpl.java - ++++ build.gradle - ++++ dependencies.gradle - +++ build.gradle + ++ acme + +++ note + ++++ core + +++++ src + ++++++ main + +++++++ java + ++++++++ com.acme.fineract.note.core + +++++++++ data + ++++++++++ MyData.java + +++++++++ service + ++++++++++ MyCustomService.java + +++++ build.gradle + +++++ dependencies.gradle + ++++ service + +++++ src + ++++++ main + +++++++ java + ++++++++ com.acme.fineract.note.service + +++++++++ AcmeNoteReadService.java + +++++++++ AcmeNoteWriteService.java + +++++ build.gradle + +++++ dependencies.gradle + ++++ starter + +++++ src + ++++++ main + +++++++ java + ++++++++ com.acme.fineract.note.starter + +++++++++ AcmeNoteAutoConfiguration.java + +++++ resources + ++++++ META-INF + +++++++ spring.factories + +++++ build.gradle + +++++ dependencies.gradle } } @endsalt \ No newline at end of file diff --git a/fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml b/fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml deleted file mode 100644 index f7a61dd35..000000000 --- a/fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml +++ /dev/null @@ -1,39 +0,0 @@ -@startsalt -{ - {T - + module - ++ mymodule - +++ core - ++++ src - +++++ main - ++++++ java - +++++++ org.apache.fineract.mymodule.core - ++++++++ data - +++++++++ MyData.java - ++++++++ service - +++++++++ MyService.java - ++++ build.gradle - ++++ dependencies.gradle - +++ service - ++++ src - +++++ main - ++++++ java - +++++++ org.apache.fineract.mymodule.service - ++++++++ MyServiceImpl.java - ++++ build.gradle - ++++ dependencies.gradle - +++ starter - ++++ src - +++++ main - ++++++ java - +++++++ org.apache.fineract.mymodule.starter - ++++++++ MyAutoConfiguration.java - ++++ resources - +++++ META-INF - ++++++ spring.factories - ++++ build.gradle - ++++ dependencies.gradle - +++ build.gradle - } -} -@endsalt \ No newline at end of file diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index e09bf3e92..07952b14e 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -161,10 +161,6 @@ dependencies { // testImplementation( 'io.cucumber:cucumber-spring', 'io.github.classgraph:classgraph', - project(':module:dummy:core'), - project(':module:dummy:service'), - project(':module:dummy:starter'), - project(':custom:foo:service'), ) testImplementation ('org.springframework.boot:spring-boot-starter-test') { exclude group: 'com.jayway.jsonpath', module: 'json-path' @@ -173,6 +169,6 @@ dependencies { exclude group: 'javax.activation' exclude group: 'org.skyscreamer' } - testImplementation ('org.mockito:mockito-inline:4.8.0') + testImplementation ('org.mockito:mockito-inline') } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java index ed7e15cdc..ca76bf94a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java @@ -45,7 +45,7 @@ public class ShareProductDividendAssembler { private final ShareAccountReadPlatformService shareAccountReadPlatformService; @Autowired - public ShareProductDividendAssembler(final ShareProductReadPlatformServiceImpl shareProductReadPlatformService, + public ShareProductDividendAssembler(final ShareProductReadPlatformService shareProductReadPlatformService, final ShareAccountReadPlatformService shareAccountReadPlatformService) { this.shareProductReadPlatformService = shareProductReadPlatformService; this.shareAccountReadPlatformService = shareAccountReadPlatformService; diff --git a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml index 5bc9e3cd3..9f9c3b880 100644 --- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml +++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml @@ -29,4 +29,5 @@ + diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java deleted file mode 100644 index ccbacd785..000000000 --- a/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.module.example; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.cucumber.java8.En; -import org.apache.fineract.dummy.core.data.DummyMessage; -import org.apache.fineract.dummy.core.service.DummyService; -import org.apache.fineract.dummy.starter.DummyAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -public class DummyServiceStepDefinitions implements En { - - private DummyMessage message; - - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(DummyAutoConfiguration.class)); - - public DummyServiceStepDefinitions() { - Given("/^A dummy service configuration (.*)$/", (String configurationClass) -> { - contextRunner = contextRunner.withUserConfiguration(Class.forName(configurationClass.trim())); - }); - - When("The user gets the dummy service message", () -> { - contextRunner.run((ctx) -> { - assertThat(ctx).hasSingleBean(DummyService.class); - this.message = ctx.getBean(DummyService.class).getMessage(); - }); - }); - - Then("/^The dummy service message should match (.*)$/", (String msg) -> { - assertThat(this.message).isNotNull(); - assertThat(msg).isEqualTo(this.message.getMessage()); - }); - } -} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java deleted file mode 100644 index 6978660ad..000000000 --- a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.module.example; - -import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -@EnableConfigurationProperties({ FineractProperties.class }) -public class TestDefaultConfiguration {} diff --git a/module/build.gradle b/module/build.gradle deleted file mode 100644 index d1454985c..000000000 --- a/module/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -description = 'Fineract Module' diff --git a/module/dummy/build.gradle b/module/dummy/build.gradle deleted file mode 100644 index 016d5b2c3..000000000 --- a/module/dummy/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -description = 'Fineract Module: Dummy' diff --git a/module/dummy/starter/dependencies.gradle b/module/dummy/starter/dependencies.gradle deleted file mode 100644 index cb70e5d10..000000000 --- a/module/dummy/starter/dependencies.gradle +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -dependencies { - implementation(project(':module:dummy:core'), - project(':module:dummy:service'), - 'org.springframework.boot:spring-boot-starter') - testImplementation('io.cucumber:cucumber-spring') -} diff --git a/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java b/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java deleted file mode 100644 index ffcd1be6d..000000000 --- a/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.dummy.starter; - -import org.apache.fineract.dummy.core.service.DummyService; -import org.apache.fineract.dummy.service.DummyServiceImpl; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConditionalOnMissingBean(DummyService.class) -public class DummyAutoConfiguration { - - @Bean - public DummyService dummyService() { - return new DummyServiceImpl(); - } -} diff --git a/settings.gradle b/settings.gradle index 078473734..6d87daf20 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,10 +17,6 @@ * under the License. */ rootProject.name='fineract' -include ':module:dummy:core' -include ':module:dummy:service' -include ':module:dummy:starter' -include ':custom:foo:service' include ':fineract-provider' include ':fineract-war' include ':integration-tests' @@ -29,3 +25,19 @@ include ':oauth2-tests' include ':fineract-client' include ':fineract-doc' include ':fineract-avro-schemas' +// NOTE: custom Docker image with all custom modules included +include ':custom:docker' +// NOTE: dynamically load custom modules with pattern "custom -> company -> category -> module" +file("${rootDir}/custom").eachDir { companyDir -> + if('build' != companyDir.name && 'docker' != companyDir.name) { + file("${rootDir}/custom/${companyDir.name}").eachDir { categoryDir -> + if('build' != categoryDir.name) { + file("${rootDir}/custom/${companyDir.name}/${categoryDir.name}").eachDir { moduleDir -> + if('build' != moduleDir.name) { + include ":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}" + } + } + } + } + } +}