FINERACT-1758: Improve custom module example

This commit is contained in:
Aleksandar Vidakovic
2022-10-14 18:57:36 +02:00
committed by Aleksandar Vidakovic
parent 388612b1ae
commit ba765f9b7a
51 changed files with 938 additions and 584 deletions
+77 -3
View File
@@ -23,6 +23,7 @@ buildscript {
retrofitVersion = '2.9.0' retrofitVersion = '2.9.0'
okhttpVersion = '4.9.3' okhttpVersion = '4.9.3'
oltuVersion = '1.0.1' oltuVersion = '1.0.1'
fineractCustomProjects = []
fineractJavaProjects = subprojects.findAll{ fineractJavaProjects = subprojects.findAll{
[ [
'fineract-api', 'fineract-api',
@@ -31,9 +32,6 @@ buildscript {
'twofactor-tests', 'twofactor-tests',
'oauth2-tests', 'oauth2-tests',
'fineract-client', 'fineract-client',
'core',
'service',
'starter',
'fineract-avro-schemas' 'fineract-avro-schemas'
].contains(it.name) ].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) { configure(project.fineractJavaProjects) {
// NOTE: order matters! // NOTE: order matters!
@@ -522,6 +536,9 @@ configure(project.fineractJavaProjects) {
exclude(module: "slf4j-api") exclude(module: "slf4j-api")
} }
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation( 'org.mockito:mockito-core', testImplementation( 'org.mockito:mockito-core',
'org.mockito:mockito-junit-jupiter', 'org.mockito:mockito-junit-jupiter',
'org.junit.jupiter:junit-jupiter-api', '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) { configure(project.fineractPublishProjects) {
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'signing' apply plugin: 'signing'
@@ -16,8 +16,10 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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' apply from: 'dependencies.gradle'
@@ -18,5 +18,5 @@
*/ */
dependencies { dependencies {
implementation('org.springframework:spring-context') implementation(project(':fineract-provider'))
} }
@@ -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";
}
}
@@ -16,8 +16,10 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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' apply from: 'dependencies.gradle'
@@ -18,7 +18,9 @@
*/ */
dependencies { dependencies {
implementation(project(':module:dummy:core'), implementation(project(':custom:acme:loan:cob'))
'org.springframework:spring-context') implementation('org.springframework.boot:spring-boot-starter')
testImplementation('io.cucumber:cucumber-spring') testImplementation(project(':fineract-provider'))
testImplementation('org.springframework.boot:spring-boot-starter-jdbc')
testImplementation('org.springframework.boot:spring-boot-starter-data-jpa')
} }
@@ -16,12 +16,13 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@EnableConfigurationProperties({ FineractProperties.class }) @AutoConfiguration
@ComponentScan("com.acmecorp.fineract.foo") @ComponentScan("com.acme.fineract.loan.cob")
public class TestFooConfiguration {} @ConditionalOnProperty("acme.loan.enabled")
public class AcmeLoanAutoConfiguration {}
@@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.fineract.dummy.starter.DummyAutoConfiguration com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration
@@ -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<Loan> businessStep;
private TreeMap<Long, String> 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<COBBusinessStep<Loan>>) 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);
});
}
}
@@ -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);
}
}
@@ -16,11 +16,18 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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();
} }
@@ -17,15 +17,4 @@
# under the License. # under the License.
# #
Feature: Example Modules # test properties
@modules
Scenario Outline: Verify that the dummy service returns the correct message
Given A dummy service configuration <configurationClass>
When The user gets the dummy service message
Then The dummy service message should match <message>
Examples:
| configurationClass | message |
| org.apache.fineract.module.example.TestDefaultConfiguration | Hello: DEFAULT DUMMY! |
| org.apache.fineract.module.example.TestFooConfiguration | Hello: FOO! |
@@ -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
@@ -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 <autoConfigurationClass> and a service configuration <configurationClass>
When The user retrieves the step service with step class <stepClass> and name <stepName>
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 |
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration scan="false">
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>false</resetJUL>
</contextListener>
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<encoder>
<pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.transaction" level="INFO" />
<logger name="org.springframework.data.convert" level="ERROR" />
<logger name="org.springframework.http.converter.json" level="ERROR" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -16,8 +16,10 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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' apply from: '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')
}
@@ -16,15 +16,25 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.module.service.custom; package com.acme.fineract.portfolio.note.service;
import java.util.Collection; import java.util.Collection;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.portfolio.note.data.NoteData; import org.apache.fineract.portfolio.note.data.NoteData;
import org.apache.fineract.portfolio.note.service.NoteReadPlatformService; 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; import org.springframework.stereotype.Service;
@Slf4j
@Service @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 @Override
public NoteData retrieveNote(Long noteId, Long resourceId, Integer noteTypeId) { public NoteData retrieveNote(Long noteId, Long resourceId, Integer noteTypeId) {
@@ -16,16 +16,26 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.note.service.NoteWritePlatformService; 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; import org.springframework.stereotype.Service;
@Slf4j
@Service @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 @Override
public CommandProcessingResult createNote(JsonCommand command) { public CommandProcessingResult createNote(JsonCommand command) {
@@ -16,8 +16,10 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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' apply from: 'dependencies.gradle'
@@ -18,7 +18,9 @@
*/ */
dependencies { dependencies {
implementation(project(':module:dummy:core'), implementation(project(':custom:acme:note:service'))
'org.springframework:spring-context') implementation('org.springframework.boot:spring-boot-starter')
testImplementation('io.cucumber:cucumber-spring') testImplementation(project(':fineract-provider'))
testImplementation('org.springframework.boot:spring-boot-starter-jdbc')
testImplementation('org.springframework.boot:spring-boot-starter-data-jpa')
} }
@@ -16,21 +16,13 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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; @AutoConfiguration
@ComponentScan("com.acme.fineract.portfolio.note")
public DummyMessage(String message) { @ConditionalOnProperty("acme.note.enabled")
this.message = message; public class AcmeNoteAutoConfiguration {}
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<include file="parts/0001_acme_note_initial.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="acme" id="1">
<createTable tableName="acme_note_dummy">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints unique="true"/>
</column>
<column name="description" type="VARCHAR(500)"/>
</createTable>
</changeSet>
</databaseChangeLog>
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.module.service; package com.acme.fineract.portfolio.note.starter;
import static org.assertj.core.api.Assertions.assertThat; 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.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
public class ServiceStepDefinitions implements En { public class AcmeNoteServiceStepDefinitions implements En {
private Class interfaceClass; private Class interfaceClass;
@@ -32,11 +32,12 @@ public class ServiceStepDefinitions implements En {
private ApplicationContextRunner contextRunner; private ApplicationContextRunner contextRunner;
public ServiceStepDefinitions() { public AcmeNoteServiceStepDefinitions() {
Given("/^An auto configuration (.*) and a service configuration (.*)$/", Given("/^An auto configuration (.*) and a service configuration (.*)$/",
(String autoConfigurationClassName, String configurationClassName) -> { (String autoConfigurationClassName, String configurationClassName) -> {
contextRunner = new ApplicationContextRunner() contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName))) .withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName)))
.withPropertyValues("acme.note.enabled", "true")
.withUserConfiguration(Class.forName(configurationClassName.trim())); .withUserConfiguration(Class.forName(configurationClassName.trim()));
}); });
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.module.service; package com.acme.fineract.portfolio.note.starter;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.module.service; package com.acme.fineract.portfolio.note.starter;
import static org.mockito.Mockito.mock; 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.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@ComponentScan("org.apache.fineract.module.service.custom") @ComponentScan("com.acme.fineract")
public class TestOverrideConfiguration { public class TestOverrideConfiguration {
// NOTE: unfortunately an abastract base class that contains all these mock functions won't work // NOTE: unfortunately an abastract base class that contains all these mock functions won't work
@@ -16,15 +16,18 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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 io.cucumber.spring.CucumberContextConfiguration;
import org.apache.fineract.dummy.core.service.DummyService; 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!");
}
} }
@@ -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
@@ -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
@@ -17,7 +17,7 @@
# under the License. # under the License.
# #
Feature: Service Modules Feature: Note Feature
@modules @modules
Scenario Outline: Verify that the dummy service returns the correct message Scenario Outline: Verify that the dummy service returns the correct message
@@ -26,8 +26,8 @@ Feature: Service Modules
Then The service class should match <serviceClass> Then The service class should match <serviceClass>
Examples: Examples:
| autoConfigurationClass | configurationClass | interfaceClass | serviceClass | | 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 | 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 | 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 | 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 | 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 | 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 | org.apache.fineract.module.service.TestOverrideConfiguration | org.apache.fineract.portfolio.note.service.NoteWritePlatformService | org.apache.fineract.module.service.custom.CustomNoteWritePlatformService | | 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 |
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration scan="false">
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>false</resetJUL>
</contextListener>
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<encoder>
<pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.transaction" level="INFO" />
<logger name="org.springframework.data.convert" level="ERROR" />
<logger name="org.springframework.http.converter.json" level="ERROR" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
+108
View File
@@ -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 <aleks@apache.org>']
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')
+88
View File
@@ -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"
-19
View File
@@ -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'
@@ -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!");
}
}
@@ -1,250 +1,82 @@
= Modules = 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 == Custom modules
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
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. 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] [plantuml, format=svg]
---- ----
include::{diagramsdir}/custom-folder-structure.puml[] 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] [source,properties]
---- ----
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ include::{rootdir}/custom/acme/note/starter/src/main/resources/META-INF/spring.factories[]
org.apache.fineract.dummy.starter.DummyAutoConfiguration
---- ----
+
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] [source,groovy]
---- ----
dependencies { include::{rootdir}/custom/acme/note/service/build.gradle[lines=19..]
implementation project(':module:dummy:core')
implementation project(':module:dummy:service')
implementation project(':module:dummy:starter')
}
---- ----
+
\... then the message object would contain NOTE: You don't need to edit `settings.gradle` to add your modules/libraries. If you follow above convention they'll get included automatically.
+
[source,text] 5. The dependency.gradle file could look something like this:
----
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...
[source,groovy] [source,groovy]
---- ----
dependencies { include::{rootdir}/custom/acme/note/service/dependencies.gradle[lines=19..]
implementation project(':module:dummy:core')
implementation project(':module:dummy:service')
implementation project(':module:dummy:starter')
implementation project(':custom:foo:service')
}
---- ----
\... 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! include::{diagramsdir}/custom-db-migration-folder-structure.puml[]
----
=== 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]
---- ----
=== Deployment === Deployment
Modules (better: the JAR files) only need to be dropped in Fineract's `libs` folder. Dynamic loading of external JARs is provided since Fineract version 1.5.0. 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 === 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.
@@ -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
@@ -2,16 +2,39 @@
{ {
{T {T
+ custom + custom
++ mycustom ++ acme
+++ service +++ note
++++ src ++++ core
+++++ main +++++ src
++++++ java ++++++ main
+++++++ org.apache.fineract.mymodule.service +++++++ java
++++++++ MyServiceImpl.java ++++++++ com.acme.fineract.note.core
++++ build.gradle +++++++++ data
++++ dependencies.gradle ++++++++++ MyData.java
+++ build.gradle +++++++++ 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 @endsalt
@@ -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
+1 -5
View File
@@ -161,10 +161,6 @@ dependencies {
// //
testImplementation( 'io.cucumber:cucumber-spring', testImplementation( 'io.cucumber:cucumber-spring',
'io.github.classgraph:classgraph', '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') { testImplementation ('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'com.jayway.jsonpath', module: 'json-path' exclude group: 'com.jayway.jsonpath', module: 'json-path'
@@ -173,6 +169,6 @@ dependencies {
exclude group: 'javax.activation' exclude group: 'javax.activation'
exclude group: 'org.skyscreamer' exclude group: 'org.skyscreamer'
} }
testImplementation ('org.mockito:mockito-inline:4.8.0') testImplementation ('org.mockito:mockito-inline')
} }
@@ -45,7 +45,7 @@ public class ShareProductDividendAssembler {
private final ShareAccountReadPlatformService shareAccountReadPlatformService; private final ShareAccountReadPlatformService shareAccountReadPlatformService;
@Autowired @Autowired
public ShareProductDividendAssembler(final ShareProductReadPlatformServiceImpl shareProductReadPlatformService, public ShareProductDividendAssembler(final ShareProductReadPlatformService shareProductReadPlatformService,
final ShareAccountReadPlatformService shareAccountReadPlatformService) { final ShareAccountReadPlatformService shareAccountReadPlatformService) {
this.shareProductReadPlatformService = shareProductReadPlatformService; this.shareProductReadPlatformService = shareProductReadPlatformService;
this.shareAccountReadPlatformService = shareAccountReadPlatformService; this.shareAccountReadPlatformService = shareAccountReadPlatformService;
@@ -29,4 +29,5 @@
<include file="tenant-store/changelog-tenant-store.xml" relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/> <include file="tenant-store/changelog-tenant-store.xml" relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/>
<include file="tenant/initial-switch-changelog-tenant.xml" relativeToChangelogFile="true" context="tenant_db AND initial_switch"/> <include file="tenant/initial-switch-changelog-tenant.xml" relativeToChangelogFile="true" context="tenant_db AND initial_switch"/>
<include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true" context="tenant_db AND !initial_switch"/> <include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true" context="tenant_db AND !initial_switch"/>
<includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false" />
</databaseChangeLog> </databaseChangeLog>
@@ -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());
});
}
}
@@ -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 {}
-19
View File
@@ -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'
-19
View File
@@ -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'
-25
View File
@@ -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')
}
@@ -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();
}
}
+16 -4
View File
@@ -17,10 +17,6 @@
* under the License. * under the License.
*/ */
rootProject.name='fineract' rootProject.name='fineract'
include ':module:dummy:core'
include ':module:dummy:service'
include ':module:dummy:starter'
include ':custom:foo:service'
include ':fineract-provider' include ':fineract-provider'
include ':fineract-war' include ':fineract-war'
include ':integration-tests' include ':integration-tests'
@@ -29,3 +25,19 @@ include ':oauth2-tests'
include ':fineract-client' include ':fineract-client'
include ':fineract-doc' include ':fineract-doc'
include ':fineract-avro-schemas' 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}"
}
}
}
}
}
}