From 83eb0842dcd3cc58b90c9925dd1a26b270b48ad8 Mon Sep 17 00:00:00 2001 From: Aleksandar Vidakovic Date: Thu, 17 Mar 2022 11:35:12 +0100 Subject: [PATCH] FINERACT-1532: Create an example for pluggability --- build.gradle | 18 +- config/spotbugs/exclude.xml | 20 ++ custom/foo/build.gradle | 19 ++ custom/foo/service/build.gradle | 23 ++ custom/foo/service/dependencies.gradle | 23 ++ .../foo/service/FooDummyServiceImpl.java | 31 +++ fineract-client/build.gradle | 3 + fineract-client/dependencies.gradle | 4 - .../src/test/resources/cucumber.properties | 1 + fineract-doc/build.gradle | 3 +- fineract-doc/src/docs/en/colophon.adoc | 1 + fineract-doc/src/docs/en/deployment.adoc | 2 +- .../en/diagrams/custom-folder-structure.puml | 17 ++ .../en/diagrams/modules-folder-structure.puml | 39 +++ .../docs/en/images/apache-fineract-logo.png | Bin 0 -> 22038 bytes fineract-doc/src/docs/en/index.adoc | 4 + fineract-doc/src/docs/en/license.adoc | 15 ++ fineract-doc/src/docs/en/modules.adoc | 250 ++++++++++++++++++ fineract-provider/dependencies.gradle | 6 +- .../core/boot/ApplicationExitUtil.java | 63 ----- .../WebFrontEndConfiguration.java | 2 +- .../loanaccount/api/LoansApiResource.java | 8 +- .../service/NoteReadPlatformServiceImpl.java | 4 - ...WritePlatformServiceJpaRepositoryImpl.java | 4 - .../note/starter/NoteAutoConfiguration.java | 53 ++++ .../apache/fineract/TestConfiguration.java | 23 +- .../example/DummyServiceStepDefinitions.java | 54 ++++ .../example/TestDefaultConfiguration.java | 25 ++ .../module/example/TestFooConfiguration.java | 27 ++ .../service/ServiceStepDefinitions.java | 58 ++++ .../service/TestDefaultConfiguration.java | 84 ++++++ .../service/TestOverrideConfiguration.java | 83 ++++++ .../custom/CustomNoteReadPlatformService.java | 38 +++ .../CustomNoteWritePlatformService.java | 49 ++++ .../src/test/resources/cucumber.properties | 1 + .../features/module/modules.example.feature | 12 + .../features/module/modules.service.feature | 14 + gradle.properties | 2 +- integration-tests/build.gradle | 4 + .../src/test/resources/cucumber.properties | 1 + module/build.gradle | 19 ++ module/dummy/build.gradle | 19 ++ module/dummy/core/build.gradle | 23 ++ module/dummy/core/dependencies.gradle | 22 ++ .../dummy/core/data/DummyMessage.java | 35 +++ .../dummy/core/service/DummyService.java | 25 ++ module/dummy/service/build.gradle | 23 ++ module/dummy/service/dependencies.gradle | 23 ++ .../dummy/service/DummyServiceImpl.java | 29 ++ module/dummy/starter/build.gradle | 23 ++ module/dummy/starter/dependencies.gradle | 24 ++ .../dummy/starter/DummyAutoConfiguration.java | 34 +++ .../main/resources/META-INF/spring.factories | 2 + oauth2-tests/build.gradle | 5 + oauth2-tests/dependencies.gradle | 6 - .../src/test/resources/cucumber.properties | 21 ++ settings.gradle | 4 + twofactor-tests/build.gradle | 5 + twofactor-tests/dependencies.gradle | 8 +- .../src/test/resources/cucumber.properties | 21 ++ 60 files changed, 1354 insertions(+), 105 deletions(-) create mode 100644 custom/foo/build.gradle create mode 100644 custom/foo/service/build.gradle create mode 100644 custom/foo/service/dependencies.gradle create 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-folder-structure.puml create mode 100644 fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml create mode 100644 fineract-doc/src/docs/en/images/apache-fineract-logo.png create mode 100644 fineract-doc/src/docs/en/license.adoc create mode 100644 fineract-doc/src/docs/en/modules.adoc delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java rename fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/{boot => config}/WebFrontEndConfiguration.java (97%) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java create mode 100644 fineract-provider/src/test/resources/features/module/modules.example.feature create mode 100644 fineract-provider/src/test/resources/features/module/modules.service.feature create mode 100644 module/build.gradle create mode 100644 module/dummy/build.gradle create mode 100644 module/dummy/core/build.gradle create mode 100644 module/dummy/core/dependencies.gradle create mode 100644 module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java create mode 100644 module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java create mode 100644 module/dummy/service/build.gradle create mode 100644 module/dummy/service/dependencies.gradle create mode 100644 module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java create mode 100644 module/dummy/starter/build.gradle create mode 100644 module/dummy/starter/dependencies.gradle create mode 100644 module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java create mode 100644 module/dummy/starter/src/main/resources/META-INF/spring.factories create mode 100644 oauth2-tests/src/test/resources/cucumber.properties create mode 100644 twofactor-tests/src/test/resources/cucumber.properties diff --git a/build.gradle b/build.gradle index 7a4cbf4f5..8bcfb0449 100644 --- a/build.gradle +++ b/build.gradle @@ -61,7 +61,7 @@ plugins { id 'com.github.jk1.dependency-license-report' version '2.1' apply false id 'org.openapi.generator' version '4.3.1' apply false id 'org.zeroturnaround.gradle.jrebel' version '1.1.11' apply false - id 'org.springframework.boot' version '2.6.4' apply false + id 'org.springframework.boot' version '2.6.5' apply false id 'net.ltgt.errorprone' version '2.0.2' apply false id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.1.13' apply false id 'com.gorylenko.gradle-git-properties' version '2.4.0' apply false @@ -99,6 +99,8 @@ allprojects { mavenCentral() } + apply plugin: 'idea' + apply plugin: 'java' apply plugin: 'io.spring.dependency-management' apply plugin: 'com.adarshr.test-logger' apply plugin: 'com.diffplug.spotless' @@ -112,10 +114,11 @@ allprojects { dependencyManagement { imports { mavenBom 'org.springframework:spring-framework-bom:5.3.17' - mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.4' + mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.5' mavenBom 'org.junit:junit-bom:5.8.2' mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.2' mavenBom 'io.cucumber:cucumber-bom:7.2.3' + mavenBom 'io.netty:netty-bom:4.1.75.Final' } dependencies { @@ -139,13 +142,13 @@ allprojects { dependency 'com.jayway.jsonpath:json-path:2.7.0' dependency 'org.apache.tika:tika-core:2.3.0' dependency 'org.apache.httpcomponents:httpclient:4.5.13' - dependency 'io.swagger.core.v3:swagger-annotations:2.1.13' dependency 'jakarta.management.j2ee:jakarta.management.j2ee-api:1.1.4' dependency 'jakarta.jms:jakarta.jms-api:2.0.3' dependency 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3' // Swagger needs exactly this version dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.6' // Swagger needs exactly this version dependency 'org.apache.activemq:activemq-broker:5.16.4' dependency 'org.apache.bval:org.apache.bval.bundle:2.0.5' + dependency 'joda-time:joda-time:2.10.14' dependency 'io.github.classgraph:classgraph:4.8.141' dependency 'org.awaitility:awaitility:4.2.0' @@ -191,6 +194,12 @@ allprojects { dependency "org.bouncycastle:bcpkix-jdk15to18:1.70" dependency "org.bouncycastle:bcprov-jdk15to18:1.70" + + dependency 'io.swagger.core.v3:swagger-annotations:2.1.13' + dependency ('io.swagger.core.v3:swagger-jaxrs2:2.1.13') { + exclude 'jakarta.activation:jakarta.activation-api' + } + dependency ('org.liquibase:liquibase-core:4.9.0') { exclude 'javax.xml.bind:jaxb-api' } @@ -299,6 +308,7 @@ allprojects { '**/*.puml', '**/*.github/**', '**/MANIFEST.MF', + '**/spring.factories', '**/*.json', '**/*.json.template', '**/*.txt', @@ -641,7 +651,7 @@ configure(project.fineractJavaProjects) { cucumber { tags = 'not @ignore' main = 'io.cucumber.core.cli.Main' - shorten = "argfile" + shorten = 'argfile' } // Configuration for spotbugs plugin diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index 19a9dc04c..b5c1ed495 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -1,4 +1,24 @@ + A@ixQ#+(R&$0XQD=n-U%V1_Zlr2(HU*D=q<|Vee~YD z@B01U_g&vIvn=Df_c_nA_kPYjXJ6k`mE~}sQalBLK)CWBq}4$n6ye7=76$N1sAQ%g z@D0OMQBE54@c5P8To?y@g6;T0?+XaTN&omp@yHi<13tubkynzzT*RTkW59X6U5yWX z#9*VT>mub~XJ=vW0{jR9F`Kc_@Vw#Xr+M%A$;{e>Z~_`^Aqf-@>`TZJCE=vGOh)3xJxT=DR@}G0je9ydg#L-hBAsD z>TN6GpE*OJ+x+k@6!s?LYhy8HO08Pq8R*(YR%HKHa&+FlmF|8bbWf?)x zoC>P=v)$Hco|=^1eVjup_yG%C1Nc>!HtL*LzzzQVICJ}x7F_SAmqMzke_o@xKG9^( z)*xVnTLiqqcn1>2`QDPMFdSK@}$6L!G;WXVL8%%#&~)ax5VNn6yF*AID}1 z=im&md*LA%v$^t0P~y_^f#j0?knxGf31&< z#r+F`U<|-okHPSv&Y=WPT{OeX2vF3Xd|Imuz_eD;ZPL5Mdf4suA6C|li`;%%0whbq z9?OID@C2*j1AAcW--M-;m#PU_iXeAXcezXVd#b}opd=^T3T)XL6<^-(zgyD5H;d0* zT7GGwfI8Ps+ybSC)w`pGf#?IXa-MplUQS>`oE5;XK#AR+!$Vre9}=w5TLu1LHDP_E z?`+Xɗ*z&~MoTcTT>P{I#DS(^TZK;q9a>V47iF4Tl~6mdJ`aK=UPnxgS9u9a@D zUOJ`?>^l?Odb=)t4epRc$pkVti~oo%Cb-CE6Bj>-eTm%^Iwu(ia%Y4$;?-k^%V`e1 zV^o|OR?>~La7_X#H`)kFLRsLfsy|4u_(bIVvmZXjVeW{C@a;5g91Y)}AYO{Y5}9Uk z?S4Ggi^d)65_^fhl4zX%5~2Xt@xKc9%Rqba=L^9dwIZDBqi&pE?*7Y15M!?Sf-aRh zc~Y7Eb2t@SDN~*x@dqOe0zON(BHk`_1Bl!h2mb;fcY!)rhNS}!lBS=)N}6s8w-%n; zd_$n+=Ln66uvzzz{8NCm)4*^_#|2(n;unH2q`l-ak~&J&y>f%OCX`=~t9^hs|q zy@|P(FM}z_vVlZLp$Ou(>G)uIymIuDCRf^21Wq~oKXJ>mWEgIo-ZM^<3>oYDVHeicU!Cc-=7 zGcDhi#CxUnf=mtvhb$WF@a58h8XDRopUL1B=I7^WSj(RaJ1`@{mACbv`-*!BBgp2_YTOViDbmkj1BV&!WY^D8jV>!ujx@ z4spNRVjew|Cpp9o=KRRvN3wDIW)$ZoF8;~oJ^tl9Hsm;6*#3!&2-Vs?+C8DEf}AE* zdtA7s6gcA&wkhRe_2b2$P?3Z>S}WftC0~82I#gH&7EokknW@^`9#+wm<_zhN8;29c z#@wdivR!JOxYw6)3UK9(8d_fF8o!<3d;B3c>TGbdHUW}VE{BM?c4w>NHYJ}=U zBX)T&&hCy^6T_)rV$u?SH9|4$rrQocp>}*((Ym4m55b10TP!@?^nAWliB;V5gJ!)$ zW@0`~Zt7>eDnAT|t1kdW;E?PvE1(+?oe2UR2?Q^VJJBTqE=~utJ7o+uTniLFqI}T- zpQU59djB!<%FM*MJykR>+5iPyXXSK&>5Den7Kyslht)qxu2ryb_<>F!ZazAuKzjKP zGi`n2qUt`-DCRL7$(O{i#O5-?6~=`nE5kQa@?rM?%~%}}oqbs}LvcsE z!!h^;{bd?+Wj)Vf;J^wI+|?meFjTi*jO6`WN*b4+#?tKX)W@QTN9JR2_GyFDmS54G zN69WLx|(CS2QH)?H)?c9`g2OgJd3&z$DEVpldg`m=JBSS zu--%pMI(uuY<$^AiM2%zjE5>C{ zF#px>M_BEF46SFcX+}4fKX)3|#~o+&d9*gug-(fiVR1{;jO89lSl(x$-Z#hQF=s_?#J5C7h*Huum)E{{XR>8QiUqZu* z&L7)BN0a`tOk`U5LEgeigjG^xnik;Xcz^?n-_zT+V17MqIjBFBV5wcnKpfQ@vwdP@ ztI7$WQKmFMg`4Lo{bgfXcxY>t{{!}I7(j^l$H+w!h25|&^@3m9hg;W5bAVK6a4uOj zg9&Q<+@4&@=nd^}?%Kb6~(Nrmn5BR_f`&zqzz!9`foczXw~`1_-l6BWU=l|us8yfpf70h;KuTZ5%<8SBs~ zH3_CLlY4>OWqg^ER+|2hlkoS+eIDhxDdCFQjl|p`!V&pMMYu7IKzzm&%l)(+p3A>l zSNu!-!?^Hoglc>gmIU+Tip;Z#5$7leme?L&fovFp?kO&DMBw6$b^-epd;}~BbG9L~HVCSB^%tX!9 zAWx47rw@q9n|seUuZPJU{XohDGz{XAGmIXpM9{_i-09IRx?TN6=^j~NszpE$9RxGD z=Z#2mC%!~KESVituC?=5hEezuu(mQWp0PuXc#pE zz5W2@9((jk9TI+oEJukfvXnAV`}o&%aY0ZNHKu~kV1k!7{B!{f63V+)OmAc*nSYV`-V``41|NUn_6LumXd3 zCN4O1*8NUrt&Cw7wW_Lx<8I$+&XaHmqx!7~a++`c6IN81^?T0r;~TY3A_Vg;52G}u zEj8n%ok);~SnTiv!hPX>DCD#42%_c;!G;y+J;-tg?|nAhywQ2Zi;d9Xia%ck9liD>ME#%sqJa5mF2L|sspRau%9LX;Umsa6d_SJE&R3q}McQ?px zJD1F`F9f15NW zo2eMwY3WP&&wWy0Rs+?;8{wC@0OtNAqw;qIOYd`2IMw6G=jzmI%W8S+Ej{YBr7qC? z7S+FQ7iRhHr8IJJaisWWyFse6mYZb)jfga3a78h0t(K0kA(dB#$1?z@`$sI1}c@Grj&u)F9uD;|Pk>FOShl*H(Mfpo!RFHbjW+N7@@*^58cBmq+E=))|IY^Zw_AC_cddiCpJKJaV8)1!dJ|>kUrG8 z43`<<%JnYZE9`(3WX$mjU4{YmJ&z2UJ`&tohDHiuy-8=?YX9m@ACN%-m&dI!9Ey%F z?Yc-8da&zStjS{UFWrBWYx9$lf)Ysy@1CN2*u{M!yk+Zwe#yV&D=Z#B`_jypU{qeK zWnAE3h|nu6^!|Ftr_Ot4sb{YRT8Hikvx;K})7f+6^q$oO6s(fVvj&A7MKP3_hefpR zSa%Zi=h(mh_%_;<%4{?1&Syzc@Wr7$)`-0Ewpe&C-6GdjfN~hxfV_WW_;G$MvEr8W z?JC})_wbQVM_6RBhvJwC-(puO=?7viGY=i*-#&wIHr!PEmv2YJ0FCF8e0E`!LM=y2%A(+}q2VinZ`|}{F@S5%>+t@HZMqi9 zYdT4Hv`tsx`H4TWaa-oQRk204)5=%ts1}V|uvLfjrPX4HqrSA-oAg-Z=e4mY`X|EJ z&aK!@ahoJ(@4|Ft^VaD0nmB}eJIc~K4sEU9e<^+MADThYu8h0<`bK|XpU98&{Y#mT zAHJDRSy5dA_lmjbjXnO#fpd#A;X~2lN#R<7-N^UF)~hu$0(h9#nM%JsoT%^+&USfQ zLf=FuT7u9$OYQFWIY>!KClc&MuJKpr@Z@5ug(~CkK4_YdVvoox?|%&*hQ@uO|NA;a zI!eV>M7XVE*{gGxu6TtJ2eHkL1;MD<6>{_Z#c{tss=>?j+Nio%>Gmo*uqskgumjT+Pds{L-Q^fR5npr;`|99#$E2+H(U1?O?}o^M2cpCb zEzH)bDnBRRvP_~=Kt12!Sa(0wr|GLylV=_OqOGoZ{-dztXsW%;mIvHtOe}9w4h8| zaGR(}f{<-qq@`9qq52nc(z)GL@U?!@A^7}Cq~oNy!qoNNirVk&n->=cXR%1`9@d_D z*k<}m3k;8;A#k0rZ(RrGtsarh0P{bq-&dSLNusQo)vkKO%MudrxQ zXV3L6_srCm?90Yl(SneDm zwcA)Xx~bS0YWaQYUdA+smAZu<2$_}J8H>VmyT<&Ex~cDj6gkfRcO0XQPw1z3e8QQ_ zlCwf_bZ!0aVdH`dc9#;ASLYSFaLd0yM-9!VoZ^hr6P_pWt4mgT?!9&1>>yU*ewe*- zkcuHBl~_g|{KrEgv0Uqlzv{^!#S7Gtn%KAK$HV!^;rQ5Bde|Op;T2qo=%QLfMzltA zVs8%5@>Vga#d+MU_z56emZPF!9o+A9Cq2t0krX>lA;7<42aayMLuQO23G>XK4syBvY9Hg8WR-R zTNk_l%*ewM?S-cB4{fnfj-oM|fKp>T+~n20kUH*)JHI4Cyt?%Gdf6)BoEm+P& zXpzyLBbME0o17Yo+#Pb4PqYfxA^oG#G49+ryMEw%+EB$sWyHJ}oUiani})CMhbxg} z1A3FU+;C#Cq9%2?{UmtqR|+xqY;4}MkT%+j#H6s9=lH!;J4)cw}VtS)S?ycn@bm2O^&yX#?9VNY3#H1 z==Ig!_t%uMKfeF^iKAy{gA20iw`yR{>av&|e<4R-&{~VWncBP*!2VVd*C8lYbcq78 zw=Dz#>*WOyVl@Os%}-a}o`@L?xdn#NAIc|iR*j+9gDq!K?m0*J?0$<^?clv?L5gGv zi@GgOC8@ldULB11D=YO1q7;h1L~->+>D@+)cgr>|(Z2}v1|G;Y!qUB#{S0-)!r+p; zy-xCjiT<&YDk=u@eW-ezZpW}4UsFgs4%hBbsLdBF4>64)<+FT^tk#Pqj=f{YikYXy z?ICu<9W`1*wY`he1q6Oh7x2-nt717k?~9V8tb;aZZ8M)d+W5$|wG9CQtRM{kD&&cl z4X8dw(pi`AQcz^b<`1bqRF4m8OxyT{5;PneHvJ(mw0-4GhyW#sYZT;`^D_TWKiw;&=ZB?Px=HLo)+UaGH!)`ErMCJsIWLVZG?mh~di@C7<3>27=AP z1k~Ru^mJrU?9CegV4;R4wYzZeD3+TQ$`uuc9T`}ME}GmcJ9t!FoLCy$>fU|+EWr1g zpzHal*dQm-bfOVLH`4FN>>+`h6(uD}11|D&-aVANv0okz88~boQj-If+fhG)CBYoZQgmB%0(c9%9EeocTo1&Gh3bD7JFG+*{mHpg2aE4OHQZ&Lw<~ zquA+Yny^5sGkDILR|!>dG(**byD6OXt)g`8US2e*k=gLwx;N#l;dcGad4Cl_RcOjX zax)$zXYUXh_RRZ5g5HaSw>T$Q62HyHAuJM@_}UL zTlDIvpf@i{vu+99)G4#VqM#U&o581p3j?#0I}}>ilmf)IJoF_!&*qY61c%1|QQnig z*^QMh8WYq`*}+Qy0Rkpu3+J6bD`pL-%{af-Yc7ix^ewrG`sqZb^)f|&pFi2V>kxEU zIuMwtg)&^)rH?m{CQ%6N*q>VSB3jI4o*fifWoUtp2*;B1J0C>@3-7 z*1bi5zphYqg0aVf!e}IIx|YJHxVh5Tr28v4FFdC88It*vRn4e)qtmF|7e8FJlo~OdUNL= zzxq$7hMYrh7hIf7kDPI&B|dmnGzpRK<~^->`s>JIpI?+{IKp4Ex4djw(S9uYP>n$x>G z#N0xQWWI4+Z8-BMMJMx>m^woshd?AXqsNJb)NEqZv_;j@-I2|D4pOknuCN%dz*Mtshx zL&l{!tyM`@G~PQ&^-&yt=o1A3)*TJd>QmVj6mv>mTji7FrQ;2vF`qaIyU$75ShBtN&*DpTNsq3d zS~iELkuyOzdZsB>Ls%Q1X`Pp9lCf!${{}An@L^KXj>yP|NA!lAf218|;R0PR$6A%gKfJ7uzMbYQ670O*-uOAniRGZs$ zBv9OGr3E)(pMhRLIi(-HukTN~eUM&!f7;i-igwejK;o6i_MLdqP@613rSJS(V~jIjrJIB?he^>k zZ`?%6AO$;<>r?qC7zz}%i7KhBOBBTsEpga!!pdRPtZA29^NhUc3B$a9GHJGa%?Hp_ zL9`tOk-F2mvpna8Rr0ptmodp=kM7exc1UIM9#69BS8(0t2qZGa{BGPj^U5$H|6^c2 zvXfO!RJS;;Sfo?*RR`XP*wJM4*LU)QG*&k_<1Vtl-;L=7O_y@=ic-yDVG&K)dByPh zeDFXCN*q?5uxi@nH997XeaNUmTQTyd4nJ=x3^K#=XJayDw|FtIF75uXVv;y*`0o`f zBC=9*&&^-M2w~Kqg}Yeytk^?U{x1uo8M|dHyLlXu`Ho+;`4gXs)I?`doLApLE?g4j zn7>{|8G`55fFZ|NyosA=Natnq%=Z1iUI1nW*FLAwcN)|4=L0J*I8P^@Wbbi){hs85 zVIiAT);39jUeniYF2}fKt4oBHrBS?oRQdyHqCs|IRz~eY2rPNz+iLbiS`hI1b{@xaKo=qY~<+8Fi#Pv z*AQVeZTLrRD`6|ukJg%I$51VQ#7RysJ0~QPbk0LQzNoS9T^@1P_bLp>0xr-H0?wUH zF{Z1~opR8RL5f*9gABm=(mq>DN-!e=Oz_p7@o z1tZ`5dxA?KQZq0=crcUk$MSDEKMtBbEPq}!>BNm^U0yTTJu@w97C0ET5Jo%30u4d5 zCmv!(1qB)Y_o>6sh~Z@P`ie@q7V4OqE+3O?)HzB2D%>bjNvpTTPBt{XA)`4TjH3Nt)U^7^0=L(s}@ z$BDvn>KM1%3Fpbvw$mpdOS>a(L*i+$G1yAL>`(f?ijPVtMn5bZL%Aq$8$973Cd``F zs!Hf2psnk1aiWF;gi9EKkw7r&hGvY#ylKQ2aLNuF&ge5t#S{kZfzH{byytv;a)>B9 z&-A_>nAY+pjKC1Z0aYoFnpbbYx*iRiHMQ4t#)>8X5sAguI6C~5wbmb#TYC0QsXLqe zlewtHNj7V8hOl zqE7y&*iqyuqr>L7mb}7MK{koUHiH6DgGMg;^Lzu;ZyAxUdkHau69v|*JE_`HDb_3* zwjc9iM)VYHz9;zn?WYdXQi>gxlr-tt*Jox5|BBK8Q<|3X+hmh(BGH$$j3)VQ`^Y)q;1;@YWo)~Z{-#*ZLZig$DXFB?Ci39jbFY0olb*{ zs1pr$7GR_#VG%UReodYJzTZTc)bVX$Rq`ZDmutejs}vZC*(uf|pUJvK4Z2}=p9dIy%%XWcbKO^*Lg0p(vM(hhcb2+3u9(r5=+m1r$4$a8= zn^}+J21!aQ-=I~I?VF_0lzyzGYSU&jCf<)}9}6U28=z;a4`~#i zE;k%ydZ^dX&}>N>E5#Q!Dz02dX?H$jgY-Z5>glMMv@S}a=$DV`s`mH<9wKM|g%Hx6 zt#9X#pDZ8rqYl6FE&txGW~2if-tn~HJNHp}CV(l-L9?U~-zy9!T)#)g4>|ucds|$k z=t*^Bt^?(!#2<{5IF^t!5!RnbsYK7JiZMDFcB@(v%3yImowS;6d~h{&8f|jhMNe9& zPthoUCs7l7Fci5!bEzbP(xF9rPQ?}(z3n*R^(8N$MpkM*SRG(Ei^BNycl?N9E1YSq zMNu|9XkHF0qe?`c#!E2fsL1#zc#qMmCQSp?h!KNdBadQC+P(HyRqQDlo7Z>E;FpZZ z(ydZSCGCH>JyU^~`*oD^BMf=x{#%xX1?!x2F)J$Mtt_Y`t1;= zJFvc4Hq70!fy0&Mo!l^eXP`4AKz0xZ#lL;7?L{Oa=m^c{!o>VQE{21kIXvWPb zB!uEjcf!fo*}n(lbx@)o5fFz_kBtJ|qrB5n7#2!d&f2_AmvkV%IJz8+K-jX z+2uBzAglJ~soh)(2{h|K6FZcLnF=fTab%b8j~Np>$4`HM&yw@W?&nKJc8!Zyp5z_% z@1Dw!p*mk#P&}BRMwN0M%D{!nqAxGebTmlWwj;*wf#sqe>6G#@H-!Zx3w9MNT5D~ z86`zSkxWV*0wLzoE>^CI8t~2Ugu+HYe!A5IfqIJeUOHM*gbtye^At)L|Ef<>n1THEfR#%@ zC8fxczMSyV?8pCMG!eNlxV<^kvyYlh5(LB69{81fs3*ToL`a<<{MTBhN3GpS^;3r= zp7htWSm$_euoW=9%V>-h;iN(jacl~01P+=ze*!}$<*4bVm6nbJmSC@a( z#WuSoWn0VbS-o-2Kb;MmTqqigAgO-KNSH7Wn&x?(^ zMutf|W?;9`b)0-!)E&I*^8|zq( zUySX}X5@3Wncf^VFIW@2RPT2J5L|dRG2lVV0hNjuXtFB+(5vH?8V&6_bd5?SSt4^5 zR#1=6%}d~C`xnC~9Q?^L7t;imE0yLfrf&ad8@_2wbIs*GdUKYC!DVrNso z`ASTwo=y90-mj(kL~}PmZ4xxJcG@2>(&#Uw*NyRcy&Rg75j^JD@Mkh*IT`F39N;X& zU)aFnG>?6pre~FN?P!v)zFv74WziD{Px4@p6(vO?%P+b}=gJMxtL_)Oc{&Ip8M>UJnQ|oTxFl$_zByH%L?PE-EMITGyTYR{uyCJU^Mo>Ycv;h{Qy&1ab7AW zD7{kODbN+yi{XmeDs4kHSqrfjbSxJdR1TW~EFqHXc1x9W52CjQiLzN@p*6fDQ~hA8 zPWZZj^4UXakBMm}*+!|Z+{>#=lW))tG zyyMytjz_OUlRkzDZsPGb*sz({sdOl)^O3aAH&#`q=|}JA@GmTe>FOiBsE3Q{P# zM(xz*u&BTWKu<1Z;AhvsM=H`TF^Yz=NBxWd)dbhALr2g?%c8s}d|EmJ=IGATKu?;#>NKbCG zoPLVH4poj<<>bi#?-V7jw%67q+z8jEL@R#Ar#hpN&7=Km(qZ{Ecm#%q#L?;ggS*m2 zRE#8=-V&Ye_c6C}{zX#B5F!6jQSJ*GpRvJgk}VcK+iPh-e)NW|bX{hk+s$(y+j>39 zWQ~pjqHJWe=BWqdUhfkV8-wdQZ>~sVBLhh-&Uoi$VLfT~&O2D1nUK?Wn0)CACN+f^ z=3gJ64DuX>%zK=wRK+G%J04E%JL9qM$ZF+t<7pa_X?c?vMKYMgk{Efj7#q@GYk$iV z!GIN(DU&$4pg6M0b0giMp9JQd)j`IBN_GpD!ot8UI^rB=w8uEBG=Gor@Od8p^+_PG z$cf!>=NzCo>BznPT0i^K6h;?lRHP`>Xvf4jc>8;j3wUg#o!T)%^EPT%#78l&0=)5(u6Ia%;ON-hX^ zXbl%Nr#TA55;cVH=%4+v`tmDrth3+A#Q>DKZ)@H1ZJ#W;CV8MB_7}-m`~?y7 z57G(@_T8os(hFfck9Wy6WEBdApxKYnyTD8HRWFjSH-SiYzSFUNLy~?3ttmMyt+E(b zS2L`?saBLVEG}Z-w%e!pXH#~3G;}t=UT)tNDGM|hkf=(Kv*G3eD|bco(IFMDIp%M3 ztK(c8LuG;z=#gP)dCrv+&Mb{3KWITR^w{HPpLNQNYKSKk?KXqxh^+RTP1gr5^PYjV z9^-8$kqJ44#8DuIKe+N!5OmlN2hV!>B+r&me#0D~pgcxvNSy;1ZEacj@w8HFfs2d5 zGZzIp#GcBJ_&hA8;I@CGUu}c5Y^AiEldd1@%wcZ6!4tY~W8K1R#c$kEl-8+;tsY4} znlrDJ+f?|$#y!9@DM-=~rydt?Be7IBPCt$rS|*lKoW}W`}^s50_qFp0z&vsqcVYo9Pq zH3SYT62-OWyh+MyQl+V&nkn%)dy~6UUakG_uK&usL`-@snrL zKO~9hfPXCFr=fCON`Gw1;Lj8SJwsa-nWprZ{7Oy@i$mO3;`{15gdoEiHa;Ju*puHK z#JlOBO(V?ui8T2=SFM-x+S29QoBEhdAVH)mzgT*;QrTjzu)5JidA81yH z9UQLfCZ#kw6Q7~c)||p0Hy*p0Jjtn5!7H$da-<#zgiNo9 zVNSKd)A%sfag1PzudWQe=eWRlBL5ptbPhKBi^g-Kc4CQfCeSd&@0g6@u?g;dipzD*R#m=$b?FY8GiehNqltiA(~6QUk{OSy>9#% zG07uBsX!jeGi?b*>X2+!^lqQTrTKyrhNmp1$l&VOZ=oSC=_Nm)Sfk{o{l=`9$7C`y zlW%hnqW&$+e(GrduS>CB1Q`#g0X0at2H{!=%4A7Jx9@9{7pWA1s2!C2OzHobLi}a9 zy+S^f>puH+k_39{(sFur^SbpEn21JDJy(D^c)&oI#I`>|8<3O#66vmPji3DPtR)p) z)F8fc?CZAAoq3^5l7QL^`uKJi&Jonx5KG<(LipW7fDTB7J4gf2`EADYsXRVGy%OgkPKe}%LVo$&8=vcwaR-8 zHAE_M{7Vg`$Us%eusQ&Oa~=%y ztjuKs4V6RUgii}*FA5a2X6F%jS{12fSC%xdV?CD5UpNIvWt}K#!CJN+u>-{fj?(I~ zl`Zr#eUd{~$RJ!+!t#fff(X{P33cQx{y&mj5;5_eb3{fqjB8ta&_U=;%5t$~XTm-c zP;uKe>BlS3mdx&cn-I7aXB7}>X7f|p+N>pskB@#Fba*q~3^5*4jAP*yOt@L;%gSJD z!8y#HV}yljUO8g3q>p(8Q74?|7Y-~OGbxt%0?V&qq&ReLTwP{lP!@$HDU_ zvZF>H##H_-RSp51ZhiDjx9R-ftkBu^ose=<74kF9sWW6{>97%f=Uw5|V80?1Gr45q zC70*}RkI%KcQe!=Rb4jVuPyaFiCj(W1~Tqo={BWN$aAM^SNxPGbh*ZONOse8zVzdn zXprF0{7|vZt8D$=i~T(4Im}IL1jwKm#e_m;lCo&~+<>or>kUg~L&Evja>_MVTz_XL zf50v>aJ%bkD7Y|&kOWZZ`zS?TLWY7vr$~AtZP~XTmkeRiO%m*u#>6JoB?(ZfLl{PY zn-<(PT?kko>H^k>F5E~EFAcJRoO1U7O>~w<`t1$Y#_-&{=i6D@@|{{Pjq*%9hiiU- z;E?}>$kHF-{Fd@3!nHA>Scel2z=8cE4qVe@fPB!Ox&vs8>K~6fgZ&$Iefv0sSw3i8 zI#{RQxDaus@BEWWwE!wE0ub;L$N$6B&hv=B&gVm#@`Mw-U4_J0#s%a6`)RCP~Okh#*#3 z53t_DTjM;Z+4mB*gc_(V1LUmgSNH)s^MCAV|a1s zTS6Mk=oao;ym(mnTlf$|htx~M9S|BsAu zqqsqWfR5T3x^KPrw_{?_x^~PELNWauw?Rpy5H#iXLJUh&xQSrnk#xfV^>m1XU6TsK z%*I?%he5_oIHI$i<&@R|mww}HrX7R7{x5h~wRwd*NTb3=hPG{RX_y&bhn8av3BJk% z$g%f7VR*u89H$<8`26`KS`Ntv%2Ht{?nN9>IDY3`KsV}FmJJs-G@G`-6eCwUX7g1Tfn!GEva`jsXAhNP2z?@K{BH}jlbt-}L%X!#m&fD}tfa_^h zU?6vFkPYS$T|gxNZiR1^U(`Y=^En^pPqkz()*Js{rL;;dU7#%sv%kx5$&BvYb$S&S zyxjn?L?l5)oTG4{I-=C!f%P~F$6xf`dVg=a8 zvKmU@#e3+=RB>V8GL6b{2u73k8!0A{1H1rc-?RUAW*nvysSWdHvni*FX#rbc?>{0Z z2+F*>`)-g|{=b?E@*D+3=&l6spX=vK3^&-P5b+lUuP%coJZt29#eh3JxhAMth%Mum z2R}fHckD`ISbT&f4&rA6%-^N7ipRXJOc%L^5MLpv6eT_$+_TVN!6kqusPdJ>7l$SL@n~B_w zjSRgX1XaW{9h*F>QX3=$D>S5t+~=h@ctgw6c7LfFA!5R2Ra}=|H+e*7KY51ONA?Z0 zUX?ibZ5>J$k4v?}X2{?F;G+jQyCUv#2uX*i4cW_YFXSA2%wK0wAANtVe=G9663 z&XWaA!0v{r{g7)SLj#7lz|$INDlf?!t5U=H69%D2?kuJ?33i38v%N+-f|>b=RNdb; za%8{2%DiTmVhN5a7>&K@(C;{;4^W0H!muUezQ3T}> zCeuRTxUL}3YbqC&ZurDasVU!OUe0SD{-Ad72W)V;i88tLcXpB%;xMuEDSn}9? z(G#(wVfApBW3b@n^rmqZk!FB1t9T18{|c57bdAxs=V~EvsQ5S!7$BMA$n*1N?T->8 zBO@IeF2{|19?kbHj>Bzr0~_j6z*a5kiF@;=D=r_^@CWrh@nBL#HfktuP7_q5%w?Ya zzu2jTMFj7T!W;lUY|C=T;6Ss1SP$pwfoJayMKpKan3Pa)&ZGi4;Clp;uJFSX@cXpc z&fjn97<-(rW#&}*xgzcj9&Kx0BDY|`{O*5aa*Gm@aTyCZGFRAurU1zsKka$6x;l1A&MQ9{(>u(DH=6kRM{tNT;h~ z{*UTQDv|WO*;Cb9noV@SjWia8Jogt>B;RyM9X5rE?EhErK=@*_ddf`?VfnjakNRkU z8~dG)NB;_SB=ku>Lw$V6fy*<)t?nyWq^*Mhea%*R3sc$1<2+0`T|Caw$9O)6s+%0J z))WIcbXRb-l8^W9?z*aO5K1%-{yGn&^0!O# z{U(-Qq0OabpzVXwIIr0+ogtaOi>^V48bZ;RaJ#X7t5;(DKHHBLU^NS;!B+ZIpK`#; z4(vCLyTUQ<_fIaUoBZA-QQ%asgN7rm=#U4sKyzTBw*O_$apz#{bp;9Ai0o=f&7-g? z3ps|Nvbesgrl$3~5IKOYwk5gYy{ufZ9NQo#_NyZ{LV!?~Y6dvFI$TvY2_7DR$A1QM zk3esp59D4pPcE$DXaX@HIKRPZQ;FCP>{8Ub=gR{=X%B;r0>h+=KKeF zBp(sBP$i)>Pst&g4LxXG9oQYnNJ=6Z{=Sy=;Be4%m+^%o zzDn)LUCj%;CoNtxmzmhFsVZuS?u70Uv;Or4u#Tc_VlHsvt?QX6%+r_27!NuvI6FBV z)je#)Wj)-AVd6a?=P{|7Uz;@)P~SiQ??h;D-Z9-PpZaevtp{~PBf=sXLId3U8440M zvH!mSNE5g0dCB=UewH!ddx1IN>%fY-XIjtje&V^ar|!PAH22339ev=;NZ;Oi%xHS{ zUn75)F1&&6Uq22+<#iPRm%>huP|u9E1aOLni=>jv0S^It0HclXHQft9cd-KpwhQQ(RPPJ3Mi54T(LaIH1w6c5*NfV)Lza9KLnEq%7r zKWrpA&Bs#5{tWoR4gzT%SVlA}UPBE6XzwshU2mDcTSYOSL}iY6bsodDu6QHGyntP0 zk2~gF?wN7eSM*3Z0@AFQ#>KqBiLn?jjhM`44ID$;q)cs(ha(7rfvR2j-~aZ7uPPp{ zVs00#rpq_WEU$pl-nASfm0JDL(=!+HHG8=lH}!>F zs_A7tfo|f)Gq{OwBj>X_Q}@+V)AM^hDI@=Gm|x#%NSJR8on%Z zea)br+e0nC7dP48l3c{}eD|h>;-wEhed@^{K7Rg8%JI}v-1cG^(P+2}=!*b5E(1R? zVNITh;)joPI?iLMWA_5T1-v67P^|#ZAUeHj3Q8MMNXBAkM)LWV_`8O3)@9v^W z7tf63cR6O0_5#_ku+zf?x{2Z1q-}?>vd`|aPhUMC?ui?~)iug8qnpVY6jB*VgNLc- z#tF_YOYsVjWV_{y+h->@Vx(#?IusZ^*2oQSN-v|EB32XA@ zD1R9v>eG^7_k^s!2NBYdc!#snEU+ITJgx(_d{d}sw&e4J;LhpgrCVlKF1|6C%@vdJ zOtaICh2S_#5X+IXVOOEdu8K!rJ-`Trti;tdR+2hXeMzQ>oM zzfxaUx?UJa@vTF<`1ZlwO!uX64ZYd#?;cz%O}+Q|^T!`KGc%*ESbOg{nS>mbNkbwc zd^ivMH1O<%HCee)zucDWBzY`#Y&Y;8;8%dri0b|V@G{Cj+jbO^vDnv-j_>)@gGU~z z#?2Jk^`%{==QCcYa^J-T-g{TWj%a+Y(}~$DWl6_gq@y{tNRQS}c*W1<+Y>lXF8ko;xtYiM@kd?$6+u*I-g4 z5BL6Wo_^vxPEB7{dbp0n0ff&y7ZJd%0G|YYYr>j5wN<~Qv6Uw*Whj3SJc-c99qjPW zqyu?`9Hk#vMDTYiT8Nb6dG9zodwINI&CcahscZ3fdH@;QW%o*jy(<;^sy1=oM*|#P zr<95_)tBb-K$cQ6zA7)dQYap-<1q|;4?->y1rJw6@NmBY{NEGS|ZXI-}U;`SuLPkPMGFOI&sBiJ%;Zy zP_-E?S&SDf`YjvN6ZqtsI?L$2<4Y^2jaV<0caIn=;_w&QC2 zUC7bhe_@`tUYKY9VwtofO4n-4%b`mcM6W==X#JgX)bV2cOZAV;EH%DYu)9EVC2i^dF zw;KVj>w&HT@E!pEE5bJ$1zB20G%8NXEKfaHZNw9Y$HxBrzJvFkAIj|p%(brTJO>s_ zq$SW*<6+lzWVP#R_QGC?Y%jq{_fcW!$<2ebI-jO7kKdeJo}f*Wa{EQQc1*kV=&7% z4~}qR?@$2D|AUtewORXAy$K zp@@F|I-)UhNYw5uHa3 z3}`hBAXhVuLra;&ZGD!VPCA|rV8xA~n2cAFj(d8rYQHYeTXj@h&){xGs0#9Q-f=PtPOOq5d!Wv5Z>gX0=nytaIeZOMCVJjf#-l<2cGOzfZH}e zw;{v(mxzG@*$Dm$UzS&A5t5XW0=%Yx?f{|-*a&n>zy$?#O+^y1gyv3pwoxb%E(5;^ zeEvon?-kHB2Y3g8Uj}}HX#BH|%<5i5Y%5X@i>m`&KSKDuZ;-+*XUIwC#FFJeE3k7&s=&+9(9a4uV^H4mB{~S{dCwnb8ew0J>$F z(Opy--BqI>xL0O!QB3)*0iOo`!&Vya70|T~b|&zsd`rEH&JO$x!maA)+zfyYjyMH1G%8EsNVRKqtXYmk-%TfcGLgRGsEE ze8d>{7bNH@RlmyU+6B0y2+2r}Xa>}t20jP;-h?%Ie!Cy2wo~pLIWXW};3t6}l{`lj zZ^&8TRr&DlbifPcC3_n(x{b|V@q0>JG!pxXd= z2Z2e14ljzEsV;%;bqRbsRdB4juGb%Zh)(L!2q~6t06#NfO}?_z4p-a3H&!?>;4|_q z+UW@QSGwOI1J@E!c$v1OBlr zu{s?ALWc4fu>>RJ_|KHxZhFDcbVfaa=!AnCzQ_4i8PD3drS&~-t z!=hdEtE2>zh_nkN(T}JMA%u|7*ir;SRHTJT7o+-N7l;fA3<|qQ#-z+xRCZHQr>!%` znSQKw=ol8$xYwDz&v{=EM-bsYLf z=_0S@BGN?;0_%WPMNjz^=*j`S$9zj0v7J4J#khav4}YVC1vInQwH*t<><9u}9~NW2 zCdty>dP)VlJb^b0n^QTD7|-E=!|v+_sqUK588O}g*A=)%KEk>LwkBDsP*{O31a@Zt zw_^EsUBtxXWLQ{0FXi^nHj~%2n({jnOU;RBQ~DMgC-^+@eUhb$2@~i-Vb=;=0W1L$ zEZsH2PIk~2cUjNxnxUerP5GTfOpN(Yz$Rb=@P*n%1iE?yZz6ELS9sT8=TUjTrvt#R zl+!=k$X?eL2U{0$DZfXtISmg2y#ky-R|Mc)1uO?rk; zTb5+$Mm`bRc--E ziU7PadR?piaOeB$%{yIW(ErbC1vr7O(Sf%dSm0GdL`%Z&4!a(#J*5bsEBdYL7=L{; zu^`rnae^6U+j?Ap6X==+c+;^sojJsu{q#HRcKEh%v_Mw|uWKuCu|qDxmpTA^3_P4< z>2|Gtfvy>WcP+32Ygvc@-cjtZdo<>C7Tp;!9-AL>889y3-&e}7Kqu9}t{(o`L%I`n>N(tn4&sOlqF*pqgHnD4I;jSBwf@-~9SRZocYCqhzP-e|D~eXp)%sG; z3fiXb2R;PeKqwCG4b1nu!iGq|2l8r72(!(EEZvYk-wIRV}T!mM}W7~@GHf5tLz%AH0R73^y3nNcAj`q5qiru76 zvdqI04LB%qYJz#Knh$J^sracjk_Iu!)-W@ymdX@LElNtIa~$U2HcZm z=}xUQflew8^kxIEVP{l%YY;f>)!lCFOl>%zi}1R3VLgTsXOsh2py`SvOFz`A66mDj z0&gLfZhQ_gWA_euwXz;0*fJ2)aPEc&n(jGZ;W8;RGoYP_H3m0)Yo_InTT zG_YQP6X>MI1m02yPeeM(9}c{3|MPHfT@PcHtqc6`j|*C#2Z43KDy94ibW-C3?=j!f zMr>z~VKMGId~16)|Lt}C)wicJh%?^zVKL@wk}Tb=RUptw%>%qyz$#!KF}-L5*iGhP z--e#3y<{I2q0~-{@xCAU09XTTO|rD2PhOytnh$pMNH-q9e6wRY{?@hPThxf}c?|dx z*aW-+e4k|LpgwVdPHJA*)utOKSo&_>Yh%cN)vmpW^*4l_`E3AR1bWpjA_;U-E%2)8 z#(lw|2=`UE9`PWnH5 zPAVest_4;Ab4LKYe&9VU#(cZpO-Z1WiVV9md@K6^!}HHpu)*0+0$UVgE(vr}Szy|~QpP~lb6iQfU9LB3Q;pp!}nyHl`);p>4L_ +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. + +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. + +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). + +1. Create a folder under `custom` and name it _mycustom_. +1. Create a folder named _service_ under `custom/mycustom`. +1. Setup folders and Gradle build files for a Java project under `service`. +1. 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::diagrams/custom-folder-structure.puml[] +---- ++ +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. + +1. 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. +1. 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 +---- + +This means if we would only include the following dependencies in our main project (e. g. `fineract-provider`)... + +[source,groovy] +---- +dependencies { + implementation project(':module:dummy:core') + implementation project(':module:dummy:service') + implementation project(':module:dummy:starter') +} +---- + +\... 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... + +[source,groovy] +---- +dependencies { + 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 + +[source,text] +---- +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] +---- + +==== Deployment + +Modules (better: the JAR files) only need to be dropped in Fineract's `libs` folder. Dynamic loading of external JARs is provided since Fineract version 1.5.0. + +==== How can I start replacing services now + +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. + +==== 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. diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 348f63d14..eb19783e5 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -135,7 +135,11 @@ dependencies { 'io.cucumber:cucumber-java8', 'io.cucumber:cucumber-junit-platform-engine', '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') { exclude group: 'com.jayway.jsonpath', module: 'json-path' diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java deleted file mode 100644 index 7406a410e..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/ApplicationExitUtil.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.boot; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ConfigurableApplicationContext; - -public abstract class ApplicationExitUtil { - - private static final Logger LOG = LoggerFactory.getLogger(ApplicationExitUtil.class); - - private ApplicationExitUtil() {} - - public static void waitForKeyPressToCleanlyExit(ConfigurableApplicationContext ctx) throws IOException { - // In some environments, System.console() is not available (e.g. "./gradlew bootRun", or in a container, or in - // Eclipse, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=122429. - // - // Also (but as a separate problem), e.g. in Eclipse, the Shutdown Hooks are not invoked on Exit with the Red - // Button of the Console view (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38016); that's a problem e.g. - // in the case of MariaDB4j, because then mysqld won't be stopped; same problem similarly applies on any other - // library else that relies on an orderly Spring Context / JVM shutdown. - // - if (System.console() == null) { - LOG.info("\nNo Console available, running until stopped by signal/Ctrl-C..."); - boolean interrupted = false; - do { - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException e) { - interrupted = true; - } - } while (!interrupted); - } else { - LOG.info("\nHit Enter to quit..."); - BufferedReader d = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); - d.readLine(); - - ctx.stop(); - ctx.close(); - } - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/WebFrontEndConfiguration.java similarity index 97% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java rename to fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/WebFrontEndConfiguration.java index 6bea4704b..7e85d1ec7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebFrontEndConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/WebFrontEndConfiguration.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.infrastructure.core.boot; +package org.apache.fineract.infrastructure.core.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 7c671a532..20418134d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -133,14 +133,13 @@ import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatfor import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.note.data.NoteData; import org.apache.fineract.portfolio.note.domain.NoteType; -import org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl; +import org.apache.fineract.portfolio.note.service.NoteReadPlatformService; import org.apache.fineract.portfolio.rate.data.RateData; import org.apache.fineract.portfolio.rate.service.RateReadService; import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -244,7 +243,7 @@ public class LoansApiResource { private final FromJsonHelper fromJsonHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final CalendarReadPlatformService calendarReadPlatformService; - private final NoteReadPlatformServiceImpl noteReadPlatformService; + private final NoteReadPlatformService noteReadPlatformService; private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService; private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; private final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService; @@ -258,7 +257,6 @@ public class LoansApiResource { private final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService; private final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService; - @Autowired public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService, final LoanProductReadPlatformService loanProductReadPlatformService, final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService, @@ -272,7 +270,7 @@ public class LoansApiResource { final DefaultToApiJsonSerializer loanScheduleToApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService, + final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformService noteReadPlatformService, final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl, final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java index fcedec45c..377e5fbc6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteReadPlatformServiceImpl.java @@ -32,18 +32,14 @@ import org.apache.fineract.infrastructure.core.service.RoutingDataSource; import org.apache.fineract.portfolio.note.data.NoteData; import org.apache.fineract.portfolio.note.domain.NoteType; import org.apache.fineract.portfolio.note.exception.NoteNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; -@Service public class NoteReadPlatformServiceImpl implements NoteReadPlatformService { private final JdbcTemplate jdbcTemplate; - @Autowired public NoteReadPlatformServiceImpl(final RoutingDataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java index 52cdca444..980335611 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java @@ -42,10 +42,7 @@ import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedExce import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatformService { private static final Logger LOG = LoggerFactory.getLogger(NoteWritePlatformServiceJpaRepositoryImpl.class); @@ -56,7 +53,6 @@ public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatf private final LoanTransactionRepository loanTransactionRepository; private final NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer; - @Autowired public NoteWritePlatformServiceJpaRepositoryImpl(final NoteRepository noteRepository, final ClientRepositoryWrapper clientRepository, final GroupRepository groupRepository, final LoanRepositoryWrapper loanRepository, final LoanTransactionRepository loanTransactionRepository, final NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java new file mode 100644 index 000000000..1bea6c8f4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java @@ -0,0 +1,53 @@ +/** + * 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.portfolio.note.starter; + +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.GroupRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer; +import org.apache.fineract.portfolio.note.service.NoteReadPlatformService; +import org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl; +import org.apache.fineract.portfolio.note.service.NoteWritePlatformService; +import org.apache.fineract.portfolio.note.service.NoteWritePlatformServiceJpaRepositoryImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class NoteAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public NoteReadPlatformService noteReadPlatformService(RoutingDataSource routingDataSource) { + return new NoteReadPlatformServiceImpl(routingDataSource); + } + + @Bean + @ConditionalOnMissingBean + public NoteWritePlatformService noteWritePlatformService(NoteRepository noteRepository, ClientRepositoryWrapper clientRepository, + GroupRepository groupRepository, LoanRepositoryWrapper loanRepository, LoanTransactionRepository loanTransactionRepository, + NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer) { + return new NoteWritePlatformServiceJpaRepositoryImpl(noteRepository, clientRepository, groupRepository, loanRepository, + loanTransactionRepository, fromApiJsonDeserializer); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java index 39a3e35eb..9ef254355 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java +++ b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.mock; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; -import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService; @@ -37,19 +36,37 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.transaction.annotation.EnableTransactionManagement; /** * Spring @Configuration which does not require a running database. It also does not load any job configuration (as they * are in the DB), thus nor starts any background jobs. For some integration tests, this may be perfectly sufficient * (and faster to run such tests). */ -@EnableConfigurationProperties({ FineractProperties.class }) +@Configuration +@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, GsonAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, + LiquibaseAutoConfiguration.class }) +@EnableTransactionManagement +@EnableWebSecurity +@EnableConfigurationProperties({ FineractProperties.class, LiquibaseProperties.class }) +@ComponentScan(basePackages = "org.apache.fineract") @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -public class TestConfiguration extends AbstractApplicationConfiguration { +public class TestConfiguration { @Bean public TenantDataSourceFactory tenantDataSourceFactory() { 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 new file mode 100644 index 000000000..ccbacd785 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.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 new file mode 100644 index 000000000..6978660ad --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +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/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java new file mode 100644 index 000000000..c22bf6313 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.module.example; + +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; + +@EnableConfigurationProperties({ FineractProperties.class }) +@ComponentScan("com.acmecorp.fineract.foo") +public class TestFooConfiguration {} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java new file mode 100644 index 000000000..175909734 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java @@ -0,0 +1,58 @@ +/** + * 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.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.java8.En; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public class ServiceStepDefinitions implements En { + + private Class interfaceClass; + + private Class implementationClass; + + private ApplicationContextRunner contextRunner; + + public ServiceStepDefinitions() { + Given("/^An auto configuration (.*) and a service configuration (.*)$/", + (String autoConfigurationClassName, String configurationClassName) -> { + contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName))) + .withUserConfiguration(Class.forName(configurationClassName.trim())); + }); + + When("/^The user retrieves the service of interface class (.*)$/", (String interfaceClassName) -> { + contextRunner.run((ctx) -> { + this.interfaceClass = Class.forName(interfaceClassName.trim()); + + assertThat(this.interfaceClass.isInterface()).isTrue(); + assertThat(ctx).hasSingleBean(this.interfaceClass); + + this.implementationClass = ctx.getBean(interfaceClass).getClass(); + }); + }); + + Then("/^The service class should match (.*)$/", (String serviceClassName) -> { + assertThat(Class.forName(serviceClassName.trim())).isEqualTo(this.implementationClass); + }); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java new file mode 100644 index 000000000..5f1bd24e0 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.module.service; + +import static org.mockito.Mockito.mock; + +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.GroupRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +@EnableConfigurationProperties({ FineractProperties.class }) +public class TestDefaultConfiguration { + // NOTE: unfortunately an abastract base class that contains all these mock functions won't work + + @Bean + public FromJsonHelper fromJsonHelper() { + return mock(FromJsonHelper.class); + } + + @Bean + public RoutingDataSourceServiceFactory routingDataSourceServiceFactory() { + return mock(RoutingDataSourceServiceFactory.class); + } + + @Bean + public RoutingDataSource routingDataSource() { + return mock(RoutingDataSource.class); + } + + @Bean + public NoteRepository noteRepository() { + return mock(NoteRepository.class); + } + + @Bean + public ClientRepositoryWrapper clientRepository() { + return mock(ClientRepositoryWrapper.class); + } + + @Bean + public GroupRepository groupRepository() { + return mock(GroupRepository.class); + } + + @Bean + public LoanRepositoryWrapper loanRepository() { + return mock(LoanRepositoryWrapper.class); + } + + @Bean + public LoanTransactionRepository loanTransactionRepository() { + return mock(LoanTransactionRepository.class); + } + + @Bean + public NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer(FromJsonHelper fromJsonHelper) { + return new NoteCommandFromApiJsonDeserializer(fromJsonHelper); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java new file mode 100644 index 000000000..dfc054421 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java @@ -0,0 +1,83 @@ +/** + * 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.service; + +import static org.mockito.Mockito.mock; + +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.GroupRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; + +@ComponentScan("org.apache.fineract.module.service.custom") +public class TestOverrideConfiguration { + // NOTE: unfortunately an abastract base class that contains all these mock functions won't work + + @Bean + public FromJsonHelper fromJsonHelper() { + return mock(FromJsonHelper.class); + } + + @Bean + public RoutingDataSourceServiceFactory routingDataSourceServiceFactory() { + return mock(RoutingDataSourceServiceFactory.class); + } + + @Bean + public RoutingDataSource routingDataSource() { + return mock(RoutingDataSource.class); + } + + @Bean + public NoteRepository noteRepository() { + return mock(NoteRepository.class); + } + + @Bean + public ClientRepositoryWrapper clientRepository() { + return mock(ClientRepositoryWrapper.class); + } + + @Bean + public GroupRepository groupRepository() { + return mock(GroupRepository.class); + } + + @Bean + public LoanRepositoryWrapper loanRepository() { + return mock(LoanRepositoryWrapper.class); + } + + @Bean + public LoanTransactionRepository loanTransactionRepository() { + return mock(LoanTransactionRepository.class); + } + + @Bean + public NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer(FromJsonHelper fromJsonHelper) { + return new NoteCommandFromApiJsonDeserializer(fromJsonHelper); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java b/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java new file mode 100644 index 000000000..7d4e5c0b0 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.module.service.custom; + +import java.util.Collection; +import org.apache.fineract.portfolio.note.data.NoteData; +import org.apache.fineract.portfolio.note.service.NoteReadPlatformService; +import org.springframework.stereotype.Service; + +@Service +public class CustomNoteReadPlatformService implements NoteReadPlatformService { + + @Override + public NoteData retrieveNote(Long noteId, Long resourceId, Integer noteTypeId) { + return null; + } + + @Override + public Collection retrieveNotesByResource(Long resourceId, Integer noteTypeId) { + return null; + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java b/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java new file mode 100644 index 000000000..ecc82db90 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.module.service.custom; + +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.stereotype.Service; + +@Service +public class CustomNoteWritePlatformService implements NoteWritePlatformService { + + @Override + public CommandProcessingResult createNote(JsonCommand command) { + return null; + } + + @Override + public CommandProcessingResult updateNote(JsonCommand command) { + return null; + } + + @Override + public CommandProcessingResult deleteNote(JsonCommand command) { + return null; + } + + @Override + public void createAndPersistClientNote(Client client, JsonCommand command) { + + } +} diff --git a/fineract-provider/src/test/resources/cucumber.properties b/fineract-provider/src/test/resources/cucumber.properties index d3b2786eb..a745f7440 100644 --- a/fineract-provider/src/test/resources/cucumber.properties +++ b/fineract-provider/src/test/resources/cucumber.properties @@ -18,3 +18,4 @@ # cucumber.publish.quiet=true +cucumber.glue=org.apache.fineract diff --git a/fineract-provider/src/test/resources/features/module/modules.example.feature b/fineract-provider/src/test/resources/features/module/modules.example.feature new file mode 100644 index 000000000..ad2be04b8 --- /dev/null +++ b/fineract-provider/src/test/resources/features/module/modules.example.feature @@ -0,0 +1,12 @@ +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 diff --git a/fineract-provider/src/test/resources/features/module/modules.service.feature b/fineract-provider/src/test/resources/features/module/modules.service.feature new file mode 100644 index 000000000..6aee6ba4f --- /dev/null +++ b/fineract-provider/src/test/resources/features/module/modules.service.feature @@ -0,0 +1,14 @@ +Feature: Service Modules + + @modules + Scenario Outline: Verify that the dummy service returns the correct message + Given An auto configuration and a service configuration + When The user retrieves the service of interface class + 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 diff --git a/gradle.properties b/gradle.properties index 11fe8a797..89f430e5c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,6 +16,6 @@ # specific language governing permissions and limitations # under the License. # -org.gradle.jvmargs=-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --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 +org.gradle.jvmargs=-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --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 --add-opens=java.rmi/sun.rmi.transport=null buildType=BUILD org.gradle.caching=true diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index f395cf979..1217e8eeb 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -28,6 +28,9 @@ configurations { apply from: 'dependencies.gradle' +// enable when all tests are migrated +tasks.cucumber.onlyIf {false} + cargo { containerId "tomcat9x" @@ -39,6 +42,7 @@ cargo { local { logLevel = 'medium' + // outputFile = file('build/output.log') installer { installConfiguration = configurations.tomcat downloadDir = file("$buildDir/download") diff --git a/integration-tests/src/test/resources/cucumber.properties b/integration-tests/src/test/resources/cucumber.properties index d3b2786eb..a745f7440 100644 --- a/integration-tests/src/test/resources/cucumber.properties +++ b/integration-tests/src/test/resources/cucumber.properties @@ -18,3 +18,4 @@ # cucumber.publish.quiet=true +cucumber.glue=org.apache.fineract diff --git a/module/build.gradle b/module/build.gradle new file mode 100644 index 000000000..d1454985c --- /dev/null +++ b/module/build.gradle @@ -0,0 +1,19 @@ +/** + * 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 new file mode 100644 index 000000000..016d5b2c3 --- /dev/null +++ b/module/dummy/build.gradle @@ -0,0 +1,19 @@ +/** + * 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/core/build.gradle b/module/dummy/core/build.gradle new file mode 100644 index 000000000..bd83b90f7 --- /dev/null +++ b/module/dummy/core/build.gradle @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +description = 'Fineract Module: Dummy Core' + +group = 'org.apache.fineract.dummy.core' + +apply from: 'dependencies.gradle' diff --git a/module/dummy/core/dependencies.gradle b/module/dummy/core/dependencies.gradle new file mode 100644 index 000000000..e819cc75b --- /dev/null +++ b/module/dummy/core/dependencies.gradle @@ -0,0 +1,22 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +dependencies { + implementation('org.springframework:spring-context') +} diff --git a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java b/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java new file mode 100644 index 000000000..67e43503e --- /dev/null +++ b/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.dummy.core.data; + +public class DummyMessage { + private String message; + + public DummyMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java b/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java new file mode 100644 index 000000000..f0ae2a66e --- /dev/null +++ b/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.dummy.core.service; + +import org.apache.fineract.dummy.core.data.DummyMessage; + +public interface DummyService { + DummyMessage getMessage(); +} diff --git a/module/dummy/service/build.gradle b/module/dummy/service/build.gradle new file mode 100644 index 000000000..8e59db880 --- /dev/null +++ b/module/dummy/service/build.gradle @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +description = 'Fineract Module: Dummy Service' + +group = 'org.apache.fineract.dummy.service' + +apply from: 'dependencies.gradle' diff --git a/module/dummy/service/dependencies.gradle b/module/dummy/service/dependencies.gradle new file mode 100644 index 000000000..517ab7d84 --- /dev/null +++ b/module/dummy/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(':module:dummy:core'), + 'org.springframework:spring-context') +} diff --git a/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java b/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java new file mode 100644 index 000000000..02eb36dd0 --- /dev/null +++ b/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.dummy.service; + +import org.apache.fineract.dummy.core.data.DummyMessage; +import org.apache.fineract.dummy.core.service.DummyService; + +public class DummyServiceImpl implements DummyService { + @Override + public DummyMessage getMessage() { + return new DummyMessage("Hello: DEFAULT DUMMY!"); + } +} diff --git a/module/dummy/starter/build.gradle b/module/dummy/starter/build.gradle new file mode 100644 index 000000000..ab64be956 --- /dev/null +++ b/module/dummy/starter/build.gradle @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +description = 'Fineract Module: Dummy Starter' + +group = 'org.apache.fineract.dummy.starter' + +apply from: 'dependencies.gradle' diff --git a/module/dummy/starter/dependencies.gradle b/module/dummy/starter/dependencies.gradle new file mode 100644 index 000000000..4c8348e1b --- /dev/null +++ b/module/dummy/starter/dependencies.gradle @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +dependencies { + implementation(project(':module:dummy:core'), + project(':module:dummy:service'), + 'org.springframework.boot:spring-boot-starter') +} 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 new file mode 100644 index 000000000..1dc7427fa --- /dev/null +++ b/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.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/module/dummy/starter/src/main/resources/META-INF/spring.factories b/module/dummy/starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..5dcbd5695 --- /dev/null +++ b/module/dummy/starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.apache.fineract.dummy.starter.DummyAutoConfiguration \ No newline at end of file diff --git a/oauth2-tests/build.gradle b/oauth2-tests/build.gradle index 1a89ab761..eebd32dd1 100644 --- a/oauth2-tests/build.gradle +++ b/oauth2-tests/build.gradle @@ -28,6 +28,9 @@ configurations { apply from: 'dependencies.gradle' +// enable when all tests are migrated +tasks.cucumber.onlyIf {false} + cargo { containerId "tomcat9x" @@ -38,6 +41,8 @@ cargo { } local { + // logLevel = 'medium' + // outputFile = file('build/output.log') installer { installConfiguration = configurations.tomcat downloadDir = file("$buildDir/download") diff --git a/oauth2-tests/dependencies.gradle b/oauth2-tests/dependencies.gradle index e8088f6e3..dc085d446 100644 --- a/oauth2-tests/dependencies.gradle +++ b/oauth2-tests/dependencies.gradle @@ -26,12 +26,6 @@ dependencies { 'org.junit.jupiter:junit-jupiter-api', 'org.bouncycastle:bcpkix-jdk15to18', 'org.bouncycastle:bcprov-jdk15to18', - 'io.cucumber:cucumber-core', - 'io.cucumber:cucumber-java', - 'io.cucumber:cucumber-java8', - 'io.cucumber:cucumber-junit-platform-engine', - 'io.cucumber:cucumber-spring', - 'com.intuit.karate:karate-junit5', ) testImplementation ('io.rest-assured:rest-assured') { exclude group: 'commons-logging' diff --git a/oauth2-tests/src/test/resources/cucumber.properties b/oauth2-tests/src/test/resources/cucumber.properties new file mode 100644 index 000000000..a745f7440 --- /dev/null +++ b/oauth2-tests/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=org.apache.fineract diff --git a/settings.gradle b/settings.gradle index 0a304eb72..4fb8b3b67 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,6 +17,10 @@ * 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' diff --git a/twofactor-tests/build.gradle b/twofactor-tests/build.gradle index a83febb4a..272fa0f32 100644 --- a/twofactor-tests/build.gradle +++ b/twofactor-tests/build.gradle @@ -28,6 +28,9 @@ configurations { apply from: 'dependencies.gradle' +// enable when all tests are migrated +tasks.cucumber.onlyIf {false} + cargo { containerId "tomcat9x" @@ -38,6 +41,8 @@ cargo { } local { + // logLevel = 'medium' + // outputFile = file('build/output.log') installer { installConfiguration = configurations.tomcat downloadDir = file("$buildDir/download") diff --git a/twofactor-tests/dependencies.gradle b/twofactor-tests/dependencies.gradle index 190bfff9c..85cb0c683 100644 --- a/twofactor-tests/dependencies.gradle +++ b/twofactor-tests/dependencies.gradle @@ -24,15 +24,9 @@ dependencies { testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"), project(path: ':fineract-provider', configuration: 'runtimeElements'), 'org.junit.jupiter:junit-jupiter-api', + 'com.icegreen:greenmail-junit5', 'org.bouncycastle:bcpkix-jdk15to18', 'org.bouncycastle:bcprov-jdk15to18', - 'io.cucumber:cucumber-core', - 'io.cucumber:cucumber-java', - 'io.cucumber:cucumber-java8', - 'io.cucumber:cucumber-junit-platform-engine', - 'io.cucumber:cucumber-spring', - 'com.icegreen:greenmail-junit5', - 'com.intuit.karate:karate-junit5', ) testImplementation ('io.rest-assured:rest-assured') { exclude group: 'commons-logging' diff --git a/twofactor-tests/src/test/resources/cucumber.properties b/twofactor-tests/src/test/resources/cucumber.properties new file mode 100644 index 000000000..a745f7440 --- /dev/null +++ b/twofactor-tests/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=org.apache.fineract