From 84983608139df6ecc711060176cdf389d0b4622a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20GREFFIER?= Date: Thu, 12 Jan 2023 11:25:59 +0100 Subject: [PATCH] Upgraded to Java 17 and Micronaut 3.8.0 (#229) * Split API and CLI (#227) * Split API and CLI * Update README.md * Update README.md * Fix Sonar * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Fix Sonar * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update * Update README.md * Update README.md * Update README.md * Update * Add build step in pull and push actions * Java 17 * Upgrade * Upgrade * Upgrade * Upgrade * Upgrade * Upgrade * Upgrade * Upgrade * Fixed test * Split API and CLI (#227) * Split API and CLI * Update README.md * Update README.md * Fix Sonar * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Fix Sonar * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update * Update README.md * Update README.md * Update README.md * Update * Upgrade * Rollback testcontainers kafka * Fix upgrade * Enforce testcontainers kafka version * Fixed inject * Fixed docker tests * Fixed docker tests * Fixed docker tests * Fixed docker tests * Disabled dev profile --- .github/workflows/on_pull_request.yml | 4 +- .github/workflows/on_push_master.yml | 4 +- .github/workflows/on_push_tag.yml | 4 +- .gitignore | 3 +- build.gradle | 56 ++-- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 263 +++++++++++------- gradlew.bat | 33 +-- .../com/michelin/ns4kafka/Application.java | 2 +- .../config/KafkaAsyncExecutorConfig.java | 97 +------ .../ns4kafka/config/KafkaStoreConfig.java | 33 +-- .../AkhqClaimProviderController.java | 37 ++- .../controllers/ApiResourcesController.java | 6 +- .../controllers/ConnectClusterController.java | 2 +- .../controllers/ConnectorController.java | 17 +- .../controllers/ConsumerGroupController.java | 13 +- .../ExceptionHandlerController.java | 2 +- .../controllers/NamespaceController.java | 4 +- .../controllers/RoleBindingController.java | 6 +- .../controllers/SchemaController.java | 15 +- .../controllers/StreamController.java | 5 +- .../ns4kafka/controllers/UserController.java | 3 +- .../acl/AccessControlListController.java | 15 +- ...essControlListNonNamespacedController.java | 4 +- .../generic/NamespacedResourceController.java | 6 +- .../generic/ResourceController.java | 2 +- .../quota/ResourceQuotaController.java | 2 +- .../ResourceQuotaNonNamespacedController.java | 2 +- .../controllers/topic/TopicController.java | 6 +- .../topic/TopicNonNamespacedController.java | 2 +- .../ns4kafka/logs/ConsoleLogListener.java | 3 +- .../ns4kafka/logs/KafkaLogListener.java | 8 +- .../kafka/DelayStartupListener.java | 2 +- .../KafkaAccessControlEntryRepository.java | 2 +- .../kafka/KafkaConnectClusterRepository.java | 4 +- .../kafka/KafkaConnectorRepository.java | 4 +- .../kafka/KafkaNamespaceRepository.java | 5 +- .../kafka/KafkaResourceQuotaRepository.java | 2 +- .../kafka/KafkaRoleBindingRepository.java | 20 +- .../repositories/kafka/KafkaStore.java | 12 +- .../kafka/KafkaStreamRepository.java | 19 +- .../kafka/KafkaTopicRepository.java | 4 +- .../security/ResourceBasedSecurityRule.java | 57 ++-- .../security/gitlab/GitlabApiClient.java | 4 +- .../gitlab/GitlabAuthenticationProvider.java | 30 +- .../gitlab/GitlabAuthenticationService.java | 16 +- .../ldap/LdapAuthenticationMapper.java | 15 +- .../LocalUserAuthenticationProvider.java | 22 +- .../services/AccessControlEntryService.java | 37 +-- .../services/ConnectClusterService.java | 36 ++- .../ns4kafka/services/ConnectorService.java | 23 +- .../services/ConsumerGroupService.java | 12 +- .../ns4kafka/services/NamespaceService.java | 12 +- .../services/ResourceQuotaService.java | 40 +-- .../ns4kafka/services/RoleBindingService.java | 7 +- .../ns4kafka/services/SchemaService.java | 104 +++---- .../ns4kafka/services/StreamService.java | 22 +- .../ns4kafka/services/TopicService.java | 24 +- .../connect/ConnectorClientProxy.java | 24 +- .../connect/client/ConnectorClient.java | 4 +- .../AccessControlEntryAsyncExecutor.java | 47 +--- .../executors/ConnectorAsyncExecutor.java | 26 +- .../executors/ConsumerGroupAsyncExecutor.java | 13 +- .../KafkaAsyncExecutorScheduler.java | 19 +- .../executors/TopicAsyncExecutor.java | 83 +++--- .../services/executors/UserAsyncExecutor.java | 22 +- .../KafkaSchemaRegistryClientProxy.java | 29 +- .../client/KafkaSchemaRegistryClient.java | 10 +- .../michelin/ns4kafka/utils/BytesUtils.java | 10 +- .../ns4kafka/utils/EncryptionUtils.java | 9 +- .../validation/ResourceValidator.java | 2 +- .../AccessControlListControllerTest.java | 55 ++-- .../ConnectClusterControllerTest.java | 2 +- .../controllers/ConnectorControllerTest.java | 27 +- .../ConsumerGroupControllerTest.java | 17 +- .../ExceptionHandlerControllerTest.java | 27 +- .../controllers/NamespaceControllerTest.java | 6 +- .../ResourceQuotaControllerTest.java | 2 +- .../RoleBindingControllerTest.java | 8 +- .../controllers/SchemaControllerTest.java | 21 +- .../controllers/StreamControllerTest.java | 17 +- .../controllers/TopicControllerTest.java | 2 +- .../TopicNonNamespacedControllerTest.java | 4 - .../AbstractIntegrationConnectTest.java | 14 +- ...AbstractIntegrationSchemaRegistryTest.java | 8 - .../integration/AbstractIntegrationTest.java | 22 -- .../integration/AccessControlListTest.java | 15 +- .../integration/ApiResourcesTest.java | 6 +- .../ns4kafka/integration/ConnectTest.java | 37 +-- .../integration/ExceptionHandlerTest.java | 26 +- .../ns4kafka/integration/LoginTest.java | 9 +- .../ns4kafka/integration/SchemaTest.java | 20 +- .../ns4kafka/integration/StreamTest.java | 11 +- .../ns4kafka/integration/TopicTest.java | 26 +- .../ns4kafka/integration/UserTest.java | 10 +- .../models/AccessControlEntryTest.java | 2 +- .../ns4kafka/models/ConnectValidatorTest.java | 2 +- .../ns4kafka/models/ConnectorTest.java | 2 +- .../ns4kafka/models/NamespaceTest.java | 2 +- .../ns4kafka/models/ObjectMetaTest.java | 2 +- .../models/ResourceValidatorTest.java | 2 +- .../ns4kafka/models/RoleBindingTest.java | 2 +- .../ns4kafka/models/TopicValidatorTest.java | 2 +- .../GitlabAuthenticationProviderTest.java | 47 ++-- .../GitlabAuthenticationServiceTest.java | 54 ++-- .../LocalUserAuthenticationProviderTest.java | 35 ++- .../ResourceBasedSecurityRuleTest.java | 80 ++++-- .../services/ConnectClusterServiceTest.java | 9 +- .../services/ConnectorClientProxyTest.java | 35 +-- .../services/ConnectorServiceTest.java | 13 +- .../services/ConsumerGroupServiceTest.java | 6 - .../KafkaSchemaRegistryClientProxyTest.java | 27 +- .../services/NamespaceServiceTest.java | 20 +- .../services/RoleBindingServiceTest.java | 31 +-- .../ns4kafka/services/SchemaServiceTest.java | 13 +- .../ns4kafka/services/StreamServiceTest.java | 3 +- .../testcontainers/KafkaConnectContainer.java | 246 ++-------------- .../SchemaRegistryContainer.java | 23 +- .../ns4kafka/utils/EncryptionUtilsTest.java | 6 +- src/test/resources/application-test.yml | 6 +- 122 files changed, 939 insertions(+), 1620 deletions(-) diff --git a/.github/workflows/on_pull_request.yml b/.github/workflows/on_pull_request.yml index 56505760..2ab884d1 100644 --- a/.github/workflows/on_pull_request.yml +++ b/.github/workflows/on_pull_request.yml @@ -14,10 +14,10 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Cache SonarCloud packages diff --git a/.github/workflows/on_push_master.yml b/.github/workflows/on_push_master.yml index 951f3a41..2f4525da 100644 --- a/.github/workflows/on_push_master.yml +++ b/.github/workflows/on_push_master.yml @@ -20,10 +20,10 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Cache SonarCloud packages diff --git a/.github/workflows/on_push_tag.yml b/.github/workflows/on_push_tag.yml index ba848de1..0d06f6e5 100644 --- a/.github/workflows/on_push_tag.yml +++ b/.github/workflows/on_push_tag.yml @@ -20,10 +20,10 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Cache Gradle packages diff --git a/.gitignore b/.gitignore index f638c261..7da00ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,4 @@ out/ .settings .classpath .factorypath -api/src/main/java/com/michelin/ns4kafka/controllers/InitController.java -api/src/main/resources/application-*.yml +src/main/resources/application-*.yml diff --git a/build.gradle b/build.gradle index 7afa27e0..ddf0b79b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ plugins { - id("com.github.johnrengelman.shadow") version "7.0.0" - id("io.micronaut.application") version "1.5.0" - id 'jacoco' - id "org.sonarqube" version "3.1.1" + id("com.github.johnrengelman.shadow") version "7.1.2" + id("io.micronaut.application") version "3.6.7" + id "jacoco" + id "org.sonarqube" version "3.5.0.2730" } group = "com.michelin.ns4kafka" @@ -12,32 +12,39 @@ repositories { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.20' - annotationProcessor 'org.projectlombok:lombok:1.18.20' + annotationProcessor("org.projectlombok:lombok") + annotationProcessor("io.micronaut:micronaut-http-validation") annotationProcessor("io.micronaut.openapi:micronaut-openapi") - annotationProcessor("io.micronaut:micronaut-management") annotationProcessor("io.micronaut.security:micronaut-security-annotations") - annotationProcessor("io.swagger.core.v3:swagger-annotations") - implementation("io.micronaut:micronaut-validation") - implementation("io.micronaut:micronaut-runtime") - implementation("javax.annotation:javax.annotation-api") implementation("io.micronaut:micronaut-http-client") - implementation("io.micronaut.openapi:micronaut-openapi") + implementation("io.micronaut:micronaut-jackson-databind") + implementation("io.micronaut.kafka:micronaut-kafka") implementation("io.micronaut.security:micronaut-security") implementation("io.micronaut.security:micronaut-security-jwt") implementation("io.micronaut.security:micronaut-security-ldap") - implementation("io.micronaut.kafka:micronaut-kafka") + implementation("io.swagger.core.v3:swagger-annotations") + implementation("jakarta.annotation:jakarta.annotation-api") + implementation("io.micronaut.rxjava3:micronaut-rxjava3") + implementation("io.micronaut.rxjava3:micronaut-rxjava3-http-client") + implementation("io.micronaut.openapi:micronaut-openapi") + + compileOnly("org.projectlombok:lombok") runtimeOnly("ch.qos.logback:logback-classic") - testAnnotationProcessor 'org.projectlombok:lombok:1.18.20' - testCompileOnly 'org.projectlombok:lombok:1.18.16' + testImplementation("org.mockito:mockito-core") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("org.testcontainers:testcontainers") + testImplementation("org.testcontainers:kafka") { + version { + strictly '1.15.3' + } + } + testImplementation("org.mockito:mockito-junit-jupiter:4.11.0") - testImplementation 'org.testcontainers:kafka:1.15.3' - testImplementation "org.testcontainers:junit-jupiter" - testImplementation 'org.mockito:mockito-inline:3.7.7' - testImplementation 'org.mockito:mockito-junit-jupiter:3.7.7' + testAnnotationProcessor("org.projectlombok:lombok") + testCompileOnly("org.projectlombok:lombok") } application { @@ -45,12 +52,12 @@ application { } java { - sourceCompatibility = JavaVersion.toVersion('11') - targetCompatibility = JavaVersion.toVersion('11') + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') } run { - //environment( "MICRONAUT_ENVIRONMENTS","dev") + //environment("MICRONAUT_ENVIRONMENTS","dev") } micronaut { @@ -62,6 +69,10 @@ micronaut { } } +dockerfile { + baseImage = "eclipse-temurin:17-jre-alpine" +} + dockerBuild { images = ["michelin/ns4kafka:"+version] } @@ -69,7 +80,6 @@ dockerBuild { tasks.withType(JavaCompile) { options.fork = true options.forkOptions.jvmArgs << '-Dmicronaut.openapi.views.spec=rapidoc.enabled=true' - } sonarqube { diff --git a/gradle.properties b/gradle.properties index 9208dc28..f79506f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -micronautVersion=2.5.9 \ No newline at end of file +micronautVersion=3.8.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 21827 zcmaI6Q*fYN6E&KNIk9cqwr$(C@x-=mTN6)gI}_VBCYk*2`7ch@S9R*#?W)~feY02h zTD^AuG}!V6SR?HZ1SOZQtQr^)5PA#{5So-EVT_dVHO!PqS_Gijr8tVDK%6&qZeU7XO?s53M}(nQWu(T*V4y~Q+IgZu`Cg|- zA^NxO&)4z&XPTQ4T(q8r1kU$+3v^INRW5@oYbsjnN+f1%-qEOBTa80WAz(KZ|xo} zjmJR^sH^9dtu)jPdVc;q{cZ@*{lgFH-^|rx5jfrUv?zo&7@6xf zqo{2J?XSS)LMbs4-JhM+oux%=2gj-LDutG->ubB)2_?)EB{+^WyZB+!7mT1{rLTY= zhBe$m_UQXkTYvIm@mXsLzO;ZaX-sd*8TOU{+u|RaQ4=3OA)fBB{i4Ff0M>x$;G=Ma zcigTy3Omv^$`Tq`q03>8Nu_CI-oZETO1CF?vujdca}q^5VwW%3jU=l>GX0P9$&0ck zdq~l*>>GvgA6Taz%F7GuSNLUoa04^fN57B& zyco@qy^}+xizUm!uOdF30KJ;UbaUDoc=X2i5;;X{GYa;D@a;d{4Jo$4RP>X?9tClm zA6c=cM=%!VTMjMk)s!gqqkA5#*o0Q?bWlKK)^^(tV3HwlK-L%B09T73kG}(|+OA-` z^lVb)kt1ER>-6ZSFd(c;kIq8KC)S!(aj2|HINyl4jgt?mD+-z(UScExUcp0v(;MW7 z^8L9qVV11`VMH~qbKYDhqq0|Re9{>1xW5V8Te9E%M&PXgi&h{f0k3Pc{q6jZ%?}_H zoWB$Olp082{{&B9j-g0t5mkg|jl>CvE}(wv3^&}%z#;L<4oA*icEVHCyrV_v8+8Of z@$FclzI0)mRJ~!yEuXL@E9{#QcM1j)91z>dP$XitO{IHxC-z@Kr}#75o26R^MTDIIu@^Iea}0XB#D?J(~_3 z7`p8Cq4U-63wntR0PH+uXw0Ih;)4~DCi1CH(GY9g!eTZolrQ9m9%L3~7}SPu?8-EC zcLo2{|54{e>ya;Y@!R=eD8mVSi?8FvUqHLI`qMWi=TI0=`Sk{KnuJ zjPi7bc_|V4WAV6OZ4_+Gs@1fbVqp|C;%OwH*_Dv0RWBbc}nZ%#zdQ64Bn# zl?%gu(t1RXAtW~S-c)6?VYP87Jk5m*%|R&;Y&h(SucL~?-dNofI3vkQUv6EhQCS#W z3oQ`_l46?W%km>bXxOW$0R5^Gi^cGDmE6>LTAV8rOKNLot}L95UJ+~aCnj&5ch`>B z%WSQ^g0oQ(0n62u2eV_bKAMLr`Suk=n|uk4rL-}Gb^Tlp-1LJADI<||x41^E5S1Y~ zb7f8!!V(lgB-nf2JU#d&oX%P6hfn>GH-9-3)(&PHu81o8+t8XiaHwuT>63bDqrmKr zMiqXD8pL&!CYDdL1$)zZq8^CPAH%Od164X8Y8J3`VI&}a99NeerQ?-0Io8TFlMB8^ zPoTgFCd2Alz9-gvYLJiKe6@P)uiO%wRLS6os1f{`BeE3zD`Wb2X;VgxhN4R0*j>M3 zi6e%iMl$DI0RDmkh*e}N)fg7o%$!@|Qyy=a*dHV66Y#zA4Zkt|uz&I}?9a`HKwBu^`J~UHFKq*{b z|8(%QtrwJn#0buu?cY8K`bV6=Z;+I8-K42=@Y2A=s@P@?oHj0`784JhgLX2=du7hZ zEd+_s#I?;ll}t~lNl)I2K&+&9G{VWdktxQ&j9D;#q^9vLwWP}@q};;cTh}+ z@l6hvdy{YvPAQxjeFbbmzZXyjBC(adii z&Qv@6@yWf)RPwzzhOqy@*n1CTsjg{ZQ{7+RL3KP~SyibD$TX!~%E$<@B+)$~v!iXJ zk9RI`3`JpEvSmh@x}~d>rRcH8@S3OPjSXPg+4Zu3-J{cJU z;jr?$h8jO&537S132!9su=0}hkqRThWP&SQWwjYCUD2l(^+)^^j9X;yY6%`K6DDmF zbWI~c%|Z}6_!EUmQ~Yfn0+SQ#tP$#s80yWSMXqV)tSK#lL`}#}WDL^JeGf{%jCTVb zIWbwl8Cmj;Jp_lKv~-B7IR9_aAy((h0oez$&~k!{gHa+fbB8PRkWyt$n&-q2{4w{2 z8y+RqHJ^P9$!O#-K0hc$-#eBx6px6u_@};{nutFw*mH>$)(~v)8Ipz>GQ|LuXWNw! z`gXl&#i7zX`e7#MDeVClSzkQQ&#DLFOpR`UIM2`={z&F^H>`&a&eB{vE955?NfPox z@<|Tub!n#hr!Kw~e693;xpM6cW;>bT+fXdPV0cjxX+a{`#s#eC}2O3AI)1&>X zv4t02&WA?qK{~D40-BYA@gsjGWmJ%^e@0_jLuHXKysqSZDQ#%=F-aSY9(2Ww4X!xw z7edknLe+}YVZ?)EO{TTfehQ0Zz8RLF03<<$9o32$Q6)0Unbv-5!0e33Vethrydn5+ zGS`SUyJx;dG)%qiC(l$vm>ieqbJb@}uwy}RTtbQ30RDhNn2h>6hCJ`qsTr8kCK8pb z@!##tV=X#LUX`;%i-aQ8L9JADw-6gCDCPp;{Lq%w2{BD;Odql);(tzY}Z9jw?UjauCZ@ z3t=Pk0QZ{}LQsEEz3bjLq!K8QtBi z?HIxS3jnbHKQ62t+{|4ZjQ^jA|1AynuW0fE6a<740tAHO|1VL;+DX;U+KIu`&e+v8 zOifpHNeJyZ60h(#nzvxWENjsTnd`2P}KL%^4&V5;wNMJi| zXQo_sGLD_Y<1-myd=#K)baM8|VFfvIb@v%NPag}}>N-G02#Gz$UjfkS)tkD+>0Ye0jar=7%=EoiXE!Hdk5ucsnxgz{njwOkA5>#;k5*orMm!FJN=e0&==H= zaYJmmFJLj=^U*DF3Y2_=%zKnr$)*oh4xXs#b8}l^bf4K4m~I*@{>q{^m=LH7ofGa|Nc4 z(^xQDF18*5=BBgx^Guv@!U9hWVE6hdVRGr&KHnIh&nPvse*UD%He0s!iIFWZ@OINn znV^>ZD~`;H5FTEoz9{?ZiwivBXn#2485!Q*8T|wyX;H!ShGH5a*X{bmRNjn(X>Wsm zqHOI~oqVf9zsEI4S8a(q-1Q~XqGOW6@67>0A@?6+Ndqu$3k7n7&BMb(ROcR@u`5p2 zA{TBp$&d~09Ws`oXJ-vdH-AJYEiM)rw&4FThDPvi)$L~k z2Lbs5^&g1;uO91#hDoW0p#*kSan;fOIdJ5JnWL&mQK9JwZQ_8EtJA_-+v*bG;K-1p ziPg-KcOq;uba$)^eTNIYEobzer7U3@@{o$Sm-{be{UiP7vw)qq;4H!aiW1-k%Y~mZ z(aHI`<=T7OeR{P`2>@Tv{j_i6VxW#}#ppweu~I4Q6S?;N+^DDb7658;2azU2c1P#} zMXd3b&}_dhMX?vJi!s54DM5!bJ^QD(;#cRqvO3tx0YwHkx) zhfrYE<9d&8=y>@DIFJw5?3hl>caaul>pI{ulD4rJd}sL-A&yKlZmQ{1K?8~xmQ%={jC-&sMHm8m%MjbPTU5tDgnye~P=vVMAk}U_- z3}`%6_aL^`i?Mf$I)0g9{KgqMvYTM(O0!e~)j1nfhqLFhE5gUeh%a0kq=rYS5eB=} z%9L0bgvo6^13JA|`eVavGufFa`EM7SZiI98BX!)*&RCb&IU6&E+f+$ralPgS|8_X+ zgXwYJ5sSV6YI$O}d-C*KXyiP3|4ywOA?e9!mz!>vL=aaj+_4qbaHWfsec#3Jo#i{o zldd<9J1RY~gsKj4#UWEnG zl8o^+z$HhKQ&zV05z34;10-2 zQG1Y{kvdAve5~~T$iAFMx!2GsKdn)Cm(Fux036D@eb*MSIP*q``Kxw^q)jjsE(-Q5 zk8+nejZm)Nq9UwdjT!Qm&(Us6yv4pD7v6vR<2Q{VqP-y@w_dOq2!X8wNTiaOB`4;@ z@J-O++F07}w zsu$PWW~)t{iS^Vz@0|A@lF$2HrXb*L2u)#bmL-haO>b2!TV7bf^pwv>c1HW*Ggfml z>I+aMiaZ+r?{v-&uG=gE0|5#6ufwqYza0i*6$?mH-*#0MNBh2(Ka+RhWE+;L(yBsX z{!eg=e-?@tmKGX)821&nf^O#IJsmvnc)6OM3m&oZWEW3!37o?tPICqF2)t>&?V%2> zZ?>kC=ArSP-*9*LxxVD?a(BNjJQf5%I^mFmNiySM7pg zLB*G8uI7rVc3GQuVr3-1w@SPB|I|~(q2A>lYyI;MA5fDt^NAwXbCOLis<7gA>3TWN ze!>{emZyy>wuSYT_DWyGjWI?Cy`J&8`3=lOW%n`Q@3Ms5`oMoyA4)YC#n`B$Q0$(c z9T`BOc>>xWEoQy@K4sN3>{HmlUS!LTo1RCUXVHcB zm(33Ufzu5Q#y5(~_8`RJ1nRher;)dPcgJXLoNb%MGq}4y&)Fz-Q7y`7(Hrpsk`xii z;5dL)UBWwHT}k>v`qTCe0^nC2>NBw;)apU!;D5mFLXj*dBx&N~QyHVxJ^QUV(CZ^8 zB?aLd-AmPfi*|te5y)5OIM13pLZ~%T&=JySbl|9VrTJr1C$iP5giHY_R$h z7}19D(p^at7}MEldBWS2IS`YE25sgtkcNi&V-$%GMSGvD`R}!tQoA`!`ZVV@$M4?% zHQ)E9^ECgl!1d;r;rEOyBgz8JKV|9_U;*$t6Fl$ZJNs(43aFa@_8J!_^g46?NXrP2 z@4H_#WeWkL6aasP4T6?B0Pc}8V{?r}b+XKQGsM}&1 zFc`s%60dhmFUfij|b}6<* zlg$yfM!Qx20EuY7Z&CDDoyOA(pc)iTuC2c8C^c5#+U5B^`TLlvB_MEyPn(f( zY)z}JVkDHw@mn~olvrC~SU$A9IR2U6>83^7+L;{|hEir{p4r&ibX9lsrE0CI18c?~ zG@Y-ntLX0jU5ChfbphuA{Ca(Qy}p3;@PHJ(&eSFxJUB*|`?vGrei_5U)LC-BZ|oc& zmUn;Tbm*jlC>b~UCC#72lpL4mf|5;x<#|~GnF95@PJ#tJYDfn?%KD{}k>Ye{8fpT) zD&#D|1Txk_4D0t+v-(D?7;g6yc&3bK(tf5xd5Y5B!QlE-Ij#o}PzJ%Wl_73|+!9vx z%O|`fKdu#BH!IivzL8ihZaDVl>CAz2z2Y_=XK>+On7>P1QDXQH1x!SV($?))lQJ1BVoVIy4x+0c=BX;0Ywr;KXvDGCY&CKH@Ns< zQ+zqIgjGGcfL>J}z~hz~a^~b5Z9la+u?q2Kgoj#wKXh=779PtzZ#!si<1gpj+0!mW zvt`R6S_5=H-@|uhzqXEaWhXQeUNk)EV)+R>_FcTqKxw%Bc4Dxd;0Sz6Qy-_*RNOEw zr&w`#YUSB}<2<2sZ6f*vgI(#g)O0$3jT1f5Mu5@0RHQT=P(OZy9h)V=QZCsC~U!nkALP)KipT z(nW?c1wms0gfx9hlb0c4e@&dB{dF(iD@Wr%SD@`t-2Z|l9A}oy#87mej;nTf+2iQ3iXm>@Do$U!qbcW2WMN{Q0WaaPH>dd z=E>Ygs>JtPT31iKab(Hgd26ngjp14>2FyYZ22M9*|PrIuWu>B(=Tzyl0 z${#Jj_&s-L$^L=oZ%{(&CRMU|jzqHw52XV&c#0o=eZWjw zZRspiw~!*uEeJ|Hcwlw>mzwxZzOYqs|MeLt(WeL$E-;?)zbR1ZG2WNohkTmr5nBTD z`iBv3v^auv*$oeCZ2x!w(L>3%99Y5Xd*uMR!?E|a+IBINtBEiihemDYpf4X9B;<4M zEQL&o4&k$x&_P9;Px=6v!;1G!Ij?N|r8n#Vk;7Z)&4RzkFgW)->>4^tNtLljlUK7u zkm1Sq3xT7%$Cl!*cu`bLr9&qB<$-|p1lomJqSHf&_91gn3u-YpwZd2KSzOGCH=EWy zs2!&$i-bqcas%cFjPJ4h><*DTbmqN~h+=tc;GdKL3BfUPr32YR%y+bk)(O!O-{&R{5< z&w1}kv?gn6M!+y$)_`M@8W9F3Sd|+I@)*ZHh!s?l5g2Z}$H0vMEgSCDeCva}I#P2+f_?VxJAO1jgkX`Bqg;B--;1BHKGrMnf_{Am@J?gC#xECQC4N~ z4u~6Q9uhwAN@J%=EVNq8#}6Nk2c8Em4afJAMfI>P1~vn`&tpYa_cyI|PhOYZhctWYnhODv(%D z-X`%;54AetMCSDHn*1n8CEhQj6mAvLO?n42%r2!aN8JIHlF{zEy14lj&3;wJ~9~6v>c{dY`Fq8@l$=I zCVaRg3m|;ahIf1TLeP2}hh0HUet@w4B?7MuNz6-~lhk~U*I=%tg4X6%5C5RD%g4t& z*e#BRjwuaJ$pA1yy_+(ADk%Tu6+%~OPU8ywNLKh2CzPEi;5%t*awtK(`AaaWL*nRw zP1u_CJ7N?b9x0AgLO*1|ONOs@#0HjnjFE+)ovXuh_HhD-@bMu-_0w zQ%btKMgtn+KfjA29m*doh2-H6o?#szV<7>%W`vlXqYClG$BZ$L+lq1|#F6*hG|UuhudmseFtaNM7u zCaq5ITs<(;qnp}^M3-c>^7^h6wMnF2NPNU4*`w?FIzefi!+o9bS-NsouTgyR8Js|$ z$Bmk4b#N))NGBUx4$boD(5IVG8u~$HpLD$={iu2e^qoO42v3yq3F+SpWOY_ zFMvWvsYgB%^3*?iyWkn@o`@#|BYL>pfV@ChJk8S|@H(Ojkpx}RupTv%?j#sGnp`I> z)VFWhX>9>75uZKjDJHHsI02&S8tjtj?2SV;ZB-!Gk3HbjIa~kG6Q8mkyMi0+m%Az3 zE0=nZeZEtID8YxH6uC_hOnsqB7m9<9B;ZbD#qgJIcW_SisWpxQ%ocnuI@>bJ|4}iy54^r6wFJV& zEqijzdS3`3e;`I-o;w99i=6YOP`ov%x=NMC-o9f{<3suUJXzd8sZV~)?(sQA6sV_b zn3_L;&+D#}z@lM-F_LvcylTb%qUYv93x31?h(|cMU2M_v)^iwbh5C~7U>zgSQvPhR zNB4A1sd)j<%P4xx**bI^=;xxx?jMyMv(j$g%`5tEQe^A&xyDxSH=@f&@4{rzV1w4V zc#DT$TswyBW)+Q6XqvP0H*i#$0B-v@isw3x=Nl}2Qw z&N}>i-CnV)xz!J|^!&9A&l+hHj@s($csjf~K69d_#*?mZ`A}9trP#Jpd!f}j^VL0+ z=PH~pn)t4=tjlh02f}jdAK9#KS-bApYJIe#8EXaQ`5*AV@XF#T#O=5g08J9RwRauX zWs2GMoway_aE`Y$=B^7hRc~kFXru#1!Do0nfta20*C18DPLvn_0?Vleh(IVLufaU> zR)n)I9KB6z=4&yb8_<*b67^EjOi!@25a?^BXHa`Ew%9CWB@#Ap!>rZ}hhnN`3%p8& zeoN_LZCC;JbKh3Nzq?YmK=9$*nZ*YT(mz+Th1YYUFVbxgd50r$H`D@2&PNuWVRkoK z&UyPDlywcG69FSrbd(LORq8+7@|4i;aNT;cb3qko*~3A8tuOvR^ zTw~ZgVMiy!4w&;(^ODF?lO{;~xFKiSSakbv=YOBTT!f(7*19_K0Rv&Q&P3=8p@SNs zQs`LEao(VrNwjL!O3|V*+_dTp|@SS%jCf%X-0(=JU&jFa_+54-jh>qBdfp3qMWE z5=#(fRaHTW&ALCrJekvhF%U#bSX)%H1Xp`u8jm0eAVQwpz{Q!_v;72leY)O(O_3nD zkWAwT$_HuIXxI+ZYQs3(-0Z}#4;+seC##hK17!x)^K ze)!W3&#nVAftv~6-d&g|5eN4r_M=32c(z_Z#cr6iX}|I%?(94?R=7d&ICJe5t%d}g z=0~1hZ1)5;u+2`xLh-3ivh<9>)rVQ+1J|1#XJdpdB30A)&o4C(QY~0EDZjn{=Wpm7 zWbj#fu8TUUjX{I8e$dB(`>`j=#QDJfzp78Uh0~HKZ?0ZW;2dpM?ZrEv5MSgzzopoK zu>Ah@ zBTbd7MP6epvo^j_ECX+}W$31B$&KOROaxcB(#0NK9$RKv7n%pMM<1YzAin(h#HX?X z*S{1aRaFrfoAKDVl+LS|Gt3#{$^I4B(@8Ip{IQAWp=1fj)2|*wr1TZO+D)sOp#E6t zE~}mZYOC{HGHLiCvv?!%%3DO$B$;B5R%9Hqp|GcLuK4ZTSVc3v%l!|fF8f&Ci ziC_NmTpWr?+WI!oDJ9^3_P0)&vZnnC=|OXsUw|yV6J;7y&=LI(&F>Z|1ImFW{LRRQ zNGb1P>9DQj4z^p9!(t!}H)FnU;$>GkZ#ZsNnh^D0&^&jgALow;xclGOcm_N&h~2UP zTuT_y1Z)aF`vv$nInbO!%fSd}di$Yi;(zyESy*Pt5g|Zy32iQ$BAbk z!n37YTg616pojDJ_wLu)(7&Oo8riV^ ztSlFas6;fT&?cQx;G1@cvc30JN`N2^3Z(aNoJ~dBQotNsi*~{tjg5PLLU2-YIxHc?R z3lGuWeA-JQ3V)>m6!WU;wtBA4=$j<>SgRyvm;xvBxf?wqYwh*-QZhM@gDAXsKjrY4 z!Ul8hGJW25!YHdKx%lG_n*{4NNds#e9kzArxSa4Zi74mD#&k7A>%0*yRXo~-I{a1m zK~iqRW9bT4(k=N`GB0oAvv`W;{Tk}xZ9S~`x?$b*+sY)MiGY2-S?aAcGzEo#$kw%- zk~|lsgAG@XvxGN7@)z`_!E(cx+=}#iX}v##aQ+W9{G?QS+j7*KLZap_l$Y@@jmdZ` zgT&@eYYP_884p$yE$LvDgp*h;Wtak$J0c2nyD@oK4%3+6IzB!qP8zE*4hZ}+wMH=O z4 zahPD@ohXE$Nj>2qB}zc`p5=ubtJ z^pqWG6<{9m9o|Rlg~AF-Ygw|E!Gh0Ue;n#kJ06yYceL_|PHW9L8ry&T7%Z{35kt3N zw+OJ-#cX&Ob1N-nZJ)WY+32O`#UDI&Xi*n&nAlbyuGS0LPAKR$OU|Av_Ubq! zr!mj0mo={$;7hgMs2}P$qtEU@(ruPj_UB@S#2?$k=;`ZLUt_-B!t$?Y zL1f!9Jl6&0KV9jGc(fr>(vu!rb*ov1f&wKHBs$3q)xX@-M=<;#(7T!fXLFS|2W@aq zRdvTFcFerjm>tjc(og7T%?Xi}Y`$X-GdxTaf3vaYTRywjdQSzjV~Utq z!{CROf;YeqJ~pdJ6@fz)Zz)k_)yPy9{B9uYt$F#wvpxDN3^BCppj~;j?y`WnunchB zvL7*F(1PpF>oht6^E?Y4q(Tix&tbRc&uR1CFLyTwYd#0{ci=D@u)2AiERIA6jYvT% z-Kg-{wO^QOuB3Y;?%L0qVKB-6>jiz$IojUC@y)l16eWS9obr1E`NBS6DX$rFs~?h$ zemJmb@w!{h^XWtSpD)#|G$*D3?mCd3!#J4kH*6?3HAQ1(4w3Wk*0Pm08t;Nw+a?ya z)}&3Eo-Nu>(o0jJfn!#;vdECfp6D-9W%WS?lF;Mov>YOdXE|pQ?^4Tn-ubpzAF}^b zAJ`oE4Z{bH2sT-ELzQD@Xr*JWn702Cncp+GR_?hJ7I@Wkx#a*n-7lN~g-g)|&u5h@RKWa^vBa~QvOsQxLF_Te-O!AQi zmOp>-m*_oV=yp@Y_M1)PxgN;x|I=mKe z^p^O|mVYcdekgy(F5DXl9iGFlVU_%RtSrt{gN}D9W5gYK1EV~7#+~#Q`i1vo=y(| z`}k+INxbx~3s1O`&o(0AU_l7R_;f3^)~&AW_IRly|Dh?MUq6>G1R>#*dAlQe)MFXNK^NxNcIaln;G%%YB~uZV1n=S50Q2BVUT7_92X@7Lgk;nO z^Hq7pElB>LKoe+)2`Lah%yEUe>0HAV7(((hj;(5Nw6TK_*%?Fm9-6;u&RC4^hdy7F z^1Whg)FYF=9AU8BhDK8OcIX~z=o7!1en9ee;O{4NBI&=?V=W0fJ zTe81?6?i2?`8vU=mm(@g*=$9FksagV=uaGX#C4z*zs~+tAyWf3tb_ar{*r-{Kk>(n zq;V$e$0suR5loSZk%<*Zj8%r70Q>0xshrw;IWmElW&yqe9P zX$XWzLvpcjB=vmoOK$iehxlVuLw(L_vR`!ezn&Ky1dqOd{IAiqaIAdc``@=RY)BB0 zAN2n@UHB=d0z`lv+^XXzNSO?EnJ#QL(g;=#lDrV3P4?b)BfPn>^@KysKT^kzd9mV! zVOhpc znkOgpkoW2v&d$qp#8Cgz#jd+ahFAZ6Ry(t~8q9VagBe&kEkeEk7ZN_&QZpZGHAz~| zm&)McfJ@Ce7dHG*Gwk$=*Cc^BTi)1{Urz5KOUYU)9T##bT|7LqCVnZ1)<7PBuKuuE&`)NRCR8 zOaU*K-4Vyjvx%PuR! z$3aa^;hl#mvqQj{0Xd{R;H7&-_^>SNl4EALZG^9%--6fJuQtJIo-hu@!xb5SK?dbS zUN@5ZQ$#0oh?Z6`PM;?{bv%dkwKz~(1^x$xEnPC0o^%8jmK$@NDMU9gTbfIib=@vB z;0ZVyVqvsOeNFCcm=6$L6A+HcUktNKSW6>lm_YK&_{S_pQP6S@Gp(``Xc^XnwH}}M zAF=GqgcpwZeY@^Xn2OD9`zFWZk=xPNi5i4_Wfc zojPk9<~tVW)Ooh&R&eH)&bjIr&VS@HlENKX7xB?AdNnBs=NCPq7*wnBGp4MilxZ`_ zS0?^+sVmU5@{5*hSUxB9tA-EgpL1VqSnLGyHUD-BTdYFAR!j{3;z@HVDOi&Rx<=)B z=ntQ9I4@iA#*Cdp*l^3ZMbGS#>*xb^=tyFwa!^YXu2k%8Ow+#wXNIp#x($wNgjc*&Kj0>P*r=wBA9~6Hmh6TF znaZyHuYw8#Q;lK)6N(g#Wa`E)V|idZy?n;9!NG35r^A7kL*wW_adjxwZowu4cvQaVo*mO+8r5LR+Mgz=HkI=ob4Qw_IRllR%n$ zh_{Y?KT>^d$ALWMawfCNtH!xx-sukl(Wx$SeAsoGoMqbY1if3viKKOg-N~c61WzqF z)a*g#8g6v^7L*)$xoC)kYYVfQEa)j;pLtu)2;)xddP?3l$X6fVK^A*kcP?vIde1bY zoTZ_{y#0E$!PcRBE&EQ>Cnums!i}WdYA(-`M$kqju@Y>Ia?qaIdp9|fDb90zjIP^a zs$7DOdRBjN(VjuCxs@EXt_N_dx&SJkQ#_LORf zlPv0}BE>Inpgd$7P{!4aHS;yE8!f=cV7=~*t}DgzlwQ{wh^OM>(~aSg2tl6q;5C;( zQ-e*uS1aCD`KfI6{GxT;wo+vAi42v3#C{fEp||h|L9fMQJO+%H;qb?a>I~{LFDZ~a zy?v6-Od`#}0hM(?wG*8dTi>QnKdiq{kD*38~F*ez-=W&9~!# z@PfeC7*2M|aauscu$f4eVcbJ;{P*GznbHVyHNm@e5DMkAcJ|8TOyd!Ng+h3HVcCf> z-frSi%xDWUkeoN)}RDAVE| z^GAy|AdQ7TvMRTj(UG3tbH_u*wJe{+=aFI{T>iPJ_zIyOEm2DBq4ja;=!(iA#^$3SsQ*CzF2%kqG zZaw$|a}2B+W9!H!$X!M*(1_Y*%~uOx=xj!C7SQF)aYfIJ;Z!~PU?LPW>nT+6TaX=f znHpy=2fvTeF?d|k(}ET(E`Ymr1~Wvi*n?R-8|P^bclWKRa}b z5(aDCEv$Ka&MxIabc zjp&OHJT@4$`a}rnn|Q_P$+%^G3kVR(_J@3ZoPk7~r) z9|aCPkkA|K!%c-*S0l_}N>4k6`@ILk-RggC+#6A{2+v=L>m)ouV4AHx&xoe6OmBs^ zxiZ_`1qc}3h45M3iTV*PWkr~i_p!$AN_qo=CC@SlyL;Vl8!%%Km4Jpo*~3 z`p%nUQUNi9oM#Fj#RI!1w^*OxYLnZ#Wq=)QdkqyqtY?=!f=4!!!x&6i)1nq_|8*CI z%?m{LOrA#LOtXpbX6%a;GV&IBTlZ<&=<=F42~KObJZ>C%?&Zfd6X&0lNYj#S%uqak zmvpd&3pTOSveTmZLN%oUClnJ(F<&O{2s@Z;n8x(@5TOhnhTr^uvLYpm2ziraq5<+; z6Nh|gjA{DBkjh(;fkiWGI#i_)mAuJR*4$s_zFo8M)HQ)}jSA>7rWEi2$&M2KY_SdU zRhjtlI_o?Vxi{2m=tB$b3`tD?k!##fw%;aqte>?5yJ;1tMnXQ?ej<)=V~YWF;Q5m1 z&KWp0LJFU*y3Hc* zOe?*T5t@2y9v8RRO+9_>0a50ZVV;m0V50qgc9~{ff^n>;B}eW0yBL~4`c}@8aLRr0 z+l^C|E!zHBKMmdStyp5L>xzAb{M&VaL&a8-KG&E0oOQ|2$Gv{n4i>;1Zw4V10J+Zg zVWQl_aW+0mML=pq0;Pd5xlVKnnqn7wN3M_R5z>y>vmL8;>GYO)cFSyAHo|fu%Gf1{ zqGUd}l=2O2uhpwBqlk-5zrgu5AK!rAD^J5<>o$^zxT~)3(LUIgOWl>CcyVt2Y`SL2 zSXlj&F>lkQ4}b2iC=5tJSRlYP?CVuX!(PT*#xmg@wfU3wWp~{xWb>-3x6bo%%jd}A zC(wEsL_TIkXKTsXZit?+>GY$;k{`AiV^i*rNf3<63wu@-R&>ZP4=QQPmZN9*Z!+4s z!Ke1)%412!n7I$83zR5lbB3-|IOJ1;;V1=n`w1^HbKQg6k~ZMzBWC4N>=U%}Q|D06 zS{@X9ED~9ves;NIzGD4{Sf1O_Ur9V>CF_b!)wbyhdKZ^qRE~FIk;J?x-EZmSc+$MU$yw`&Sp5gmoAZdqXT_X+erbn6=li? zdpXPpIoJ4$@eO!#N>N#pB;FBv`gKE#W4YyOt5vk!uto=&&#By`?$Kv>;Dl73L}xbm z!+~`H6tM}ZHjUGZv|5U4HrSgiPe{9dLBgO~{){8#@(_TjAvO^=#>Z2~YVo;#y0l_7 zKa7N|$jWYK00n=01U}qtH|)Ww`hm z@%^bkE{~BXgsH@he5@MCP#P0?ZqjqS;6Rfc1ILFsK_7m>STdy!K`-Y)&7bMrEl~@Uplx_)OX#`nPkOiefLKbjI0f7Mnkya#?5a|$6LSR>r zlnx06q*H$T^490~o%5SHbDrzE=bC4pndgs*bKh4W&3K%dz-QVzI;2t^M}6aPAjQtq z(~0EhwU3XvB!-yv$B^OM_k6A0=85z@c7(SL(zPM`81?=3iH0#3EkEw&EN9_umS7K# zpkhSQnov}d#4c^4)i|Fex4kKBH(4J2>l}>w z&i%Xhm8!E0Xwk%_Sq2*u29e=Mv73kJoobaP+BEn2_aB3!G9S$@e5K(RP`O+X(P~m? zu+90r8dGW-FHKhN@1QA`-A{2Q3;B`6*Jp{(RFkW{>3+_q@nJu4scc&s5}CG|F}n%r z$JG=cevr(Gk`I@)3`=DTBi69G_g&}-d0`RWK=!`VD2dt5)cys-Dy=c%+*c0fR|8Ok zl0*1$L&>kyrcGMAf)irTU-iqh_((j{nj~cwGr-vD?0&Yv)kA_=Kxxtnr36vjOO3ok zsh}{a(JOlk>2I|1LQgPg#tA9v4W9HoqvLu>cdQTvkC>06Yy;osmn@@`&IBRmw z7ffk^TrW1vk!IbYMZ?InU56LcKbNJinmx$efM*8=lEzS{@0#?0*Ca6myicxqzM4_J zC60-v#)YzY+tS1;Ur{Bh1aKwK&+=^RR}m*EP^8ytbr#EhS}rkZP5shOyeJJZ?aiS; zL*?Y@(lL~kHbHEgQJ+gbDMnnzkCSe|bF;N59>{A{<|r~Yap@f{Nyut{GO;}-{bFWH zhkKXw)(Y-}PpBedLp7AT#J+f6bPK)hV245LxrO@aU3iUfh}xh)Te@*$!VHvZWbVsd z`7GaxN10L{B8e{nub1K1d>IbF)*%Gj8f^ZhQp3uY2y+WHnq4tbfJ$!La%p903~{jw zltpI-9VB`$9GWi9orKwy)n;W2^fS`uu=b&3w7aL>c&N1A$l$1bFQoHq=emkdWBBwq zScQ9=K?CUS^~IVAA()EgC(hEs)NLPTcE^S$ z{1QJ(1!UTjDzUzb#P>sqGMvazkEAQPnZYy5h zF3rsS@;bQp{f5u`X3;t|!!B2843GEdtilr^{Ko3~&Y5<$GAAvZ|B=+3q7z?e$&;0@h>-ova6lV z=g8j6$(1-<5)S973ze(S>K&m$$1#+N7Kjuzv*fgePy46G?dZ513N5a&&voA{pSi5E z9!QPf&Bm`e{(wvgE9Y)5e(Y#cZvXX2Wr=KVD60)27Cw5P@>+9P@Gr)iAa=V@GQ;CH z6)upex`c)B&%0^eX%T}EjF**64{7^-&jsF5(mG{gD>YxamnXEWO~&JG!fssq3y9T1+)4guHMPDS);3YJE6-iMZy&ag`1hTFyw<@QY>0G*HX7k zo{6BF=sJ!s%;yJgj6IT^zQR*XS~03SuS0|PD}5Cd(WQ9dra8~hGs7HD3n?F-Fc}q_ z&ZjcQP2V#06xOROUWfct%**Y{^2E8xuJ%bL+eBA3hR-bNvrUUha;@nt{V1tSbDZ~R zea&j=THU42`MsFvuo+`fGURM#qlYm>ux=;v^#z;ebX+{n$2{Fh#pL%KBg2? z6ke~u)UekcS*xxR%OQf6{7DA3+w zK^v3WMAUg#=3rcVuD-I|^^5~OzF9n&vV6IjvP0c8E~Y$wbJ29ikR%u_;=SxInr$G8 z)gW)j72tL^Wb48NaFGhh{&~&tV>1Pv!k{yrHo77gJ63Rgmr}VrUdp-3FL%h;v(Oc2 zE{Yr!zuW&}`5a@bkI^frDqdQn{zmpv zmeCYUE#8ytiLbL{P&7C4MBa4Wqsv57OTyC)6zBYyDt z#8a0Lvp0yT-bslq@)lW|@PYu@p2GChf}{s}E{w=Lb_ERT*C^<``6=U^O?p~Q>Ms(c z^R_Q#dh)qdCVfuWgNCC_ME0tfbQbE-U~>>k~Kl;$#`$CRxR zhXV}Eago`Tq$Kn-Fxpi?)V&d-D_6HFqZ`pFQj1Qawm_M@YMbR0^!-A}PVKE7j&bLN zT(N84wDsNyM(kGpw~-o}E?;6xI6Vqr7mOAoy{P8!PZm43*ltS_R3Y z?V9Da>;sn^sh2oIgssCKkfAgg_5=9(w9JL~lnqG;Md+aD2&~f2JOiLxVzhglNK8bu zM)++n3ld-F1KUQ}Kub$n41VxV^MyUbVm9a`lPZ&{AVM&r>Gs(3aTr*q|E15^kd*6) zNLe>yoTVHQBPQYFyznVw>?sNtZ%}%GTH8 zFBE#oES>WkabUajM$xL^M(wi>S%-D{THQpABM zJ!UTZaST23>D(LkML$>_3AYLg3w^dvKx8Y5V_Gwx2y+El)}) z3cLUwTS;R?__}Aw+I3!+pJ}Hm7w%-yp-Pp_*QkzV7M9=EdPdZ%4eJKAB^(~UUoxO_ zqY*hY*4=%$`r^EC98JjDQ@kQt?}6z_;GR-2$#q+9_Ej>RC2( zD~2n{(O)i_TGNAmkqGX4cBFhfv}|KTigQrI8j^Q* zl6Lm`o}6|_LMRyXBk6V20>o;_07VI2R|hteKB{;-}~?=aD5;OWqbEv zc%O{ZW==)fc}0NNhI-mb+Lmhi3)F^Y+HVJ={{AW8{`B*vH`-cCM7?L^VUZhyz)_pZqM0osX=I9hvWZ^8NkB*P~m`2PI)015W!z8Hi3Raj7fBRWmQc zcEnKby`Y>#0SBgJ@z3$Fat@eNilAdmpy|P0WuBV=1Rm(QizSHoa*~FS=_*yP~>$ zJn$Z+MX7TwsqRcBqLl+u>Sd-(e1107=6y+&P8*Uj7B`K`tM%T%-Ky8@cXiZCUTTgd zjRg2E`Y4z`54pzVcO2DLZEXoyybb8yw zf1ZP$|tCEc7Yhyc;m(inMR>IkzFe9uLJLxWb~sK;2kZi zfK(H+a+dh*jQ-nvuhw~)52`C*(;SRiKdZ5?W|XJ|ys~1lbhT$Ws91l-V57xF>=_~W zx6M)w*sN(3)g|u%GJj*8G1tOuHpb9irREkfohPYS+n=|Xnjfy8tq#2(Kn6eANbAFh z8^rEC!%ogBGGLOD+N+3c{g1FQ%DQ`JehE*D?GyDg=r8ObtXe<8H2* zvC4?+O@5m5?4M{B!Q~#(8J+dH9CYSU8;8Cmo4{qgx~uOsOl*u;Xw6!}7bbnwAsF;Z z#>}s*(7Bzs_)Wb}XL20t3!2^3X}l~vgVU9z-=joaET}6qr-AbOXal)x>$r&W`1%T& zhv*=MpUypv<(HaW7l&$SuX98Ek)#`P4#GwtYthgNF0d9`P`=b8Bi)rXO} ziOE-{M5i-t64KPh8s1;(qp_LtdSlR4@_ibAzlC z{3V#l!rybPzg3oDYw}$aVh1zidFac>`smkdy=GOJtH>!)Kyd+gzuta%i<|=y;V5qO zB0&BHJV@lIZ=JH?%Hf@F+WFsDf+|VxeDqDh9Z2Jv=LC!?TxEy-^5YhR{7FdklG}XE zuPAx(a^;R~@Wc%ztr5fea45nDqVCKs@&z7>)U4H9kBiC)RoI} zHPMt#)IX}4jmpOTs@y}`RL^ueQn6BYwjD!8m#W`e5JPsUMmxoWbqcOYcV6+?@>LEO z7y^%f>6y_=;dYHaWWzBesaYEA{ze82T{&J>jeg{fcbFb9Xb}aMe`f+UxLAFDUbeR4 zIbvmCc=VVaC9fdYXTrv^YRapIP4VDiIw2BSiS6*byKw!R$!3?(B{iqH0aMpapPChr z11rWj%t!i5kOS}NYxfBL?$A0zZrY?{2ofXfTh)IUVUj`JG?Pjta7-@LGwX89RcY_w z!T=_Z!7AnY+5g)x(Qe=z!7xz({*T)Z!S05Su>HN{heK&V*D)jzMg!K5nE{HgV6AS=@S~j3HK?Sk7Wd-hoE3VJe2m|VQlb$s*^5&w&1CzcM=Kg zBTnHY2nTJZ5Wu-hr?hlR6X26Nh4eY(AS9E8uonudPs0E~*}uXpVAl*3d`Sq&%AbZf z^I5@P(+EIH@syU$(1MmT7XfjVzo-_#t$qsGXXOE3%~NPq#(COv!7L0Y(JbA>B>(y-{kNIX->1tD*ZKdt`OVtsbUrQ+ z|L1%}-v*PR%%A}=9Lyd-00|y{Q6_ME;3BZ=OQ0N}#uqlSQ$rZg{tGiO>USC}q~Ztb zzd+%?`8fPNDngqdemm$?NIED4|E)OuH<4W^LBt1&4MV|@K^V}H1meQ_ihmjp`q7)e|ePD*K0L-`AIlv3WVTwiu*9K?I2tPO;%fK#=i@=j8;c_sV>VpJ7ghL*sO$(WwOL+Zq?!0CulF)v8%z zn~m(J+ztvpiToqI*%Gt}2Ru#cLh_^=%cTyzi`OKnmG|02$Eh@0-?wNCwms76R>qM#VQcf1}C(YR0wWiI)#OX0chV;GYnaoS1 z&}-mlCQ5Fp=~;V-?m=#8mtk)fx7V`38l?C(wwht<$1O5~(qD~=!`7_X6u@6&yc7%$ zC_kZA`GMDIr3>{B0FCh)&U(C7`ietj(^;74>FZRq+X|-Z6#@7(f4mDXe!XxmgLkHV+pJell+Ny}Wr(JqC1KL*|Zh$&RI$%a~QG~e< z0XAjqoM&EBlZ8uH+5uRH&Q#4_e)=;?43w5qS#1?|_+3kI`mVSm!oCFs+6r*`z-ebI zEBNt_0(QBVZ!Vz+BL3+_gCh1^9Z=b42GZmU-O$v9ROH^GZXW4Y5z2S&cfSB{-v|j` zaVbMog0r$O;CzheMQ04HUa30@x0p}nG|tXv_#7tIRpsAzhL>W2;1}#EHBB2&NS^Vm zHY9TMa_$G@XaL{`jk)6~@t)y=B)*z$_<{&7ad*!AAHDG($@M>qQiA!2f(H;8XX+P{Me&e zwI#!H=D!D6ex#pyt_L1wcTf^{X)?!L2{vR802wvPC~3+(jhJ!!;xu(>=9VOB08Ist z75FKfz7~8k<~CAGHw6{#IeuJ9^9^neXE#@7ZBJ7qf0fmbQPDFp`t>co&Y$j3j8PXD zdaa0nO>kl(*SKtO~+E>k|~vB5w<dSZcb` zdb3s!Ji5c$AP7EOT8S#L(RljAP0}Fks{i1w($#g?sMWv?A2F1 z+lSSSX1)5q_#wdk)YeA`KONiVebp$?j3CSi@ZBW`A4D9 z4#zZ}7_-bK|C&Jkur`7kdhk!5LiA>7En{eEtQ5PEnfXY0aQCXEWz9UD$wfIazH_wx zN*#(w2U596kBiX6lLO){Zs}?rD`O7WxNu}GPpv?aXkgqm5KpLL(q%%y$6qKvwOA!R z=nJi75ucAK^dBLapd%VD#(xT8Lg>Cn5BG>``=%&=^$ok}B#ja8QvNL{A3y#}dfzWk zU^IpAaSdMFd$1soJzM6?Fz-*A_$_>zN9M#vlp~l*Vf0J&&-rPy&L2bD_PQ-@=KR~q zD|-{3((TB5)^?1v*79et+JSy}@X692kpCqpne=vQ9uWkDhX@3O`2P_^3artF_QV}^ z^NRy%kahI>ok%6zNT)?PyqM^g*l3baNG8=S7N1P4otV~_7z|;uKP(c4x|x8w?qu>F zo+ArL=D3t&512Wd+?>?%e_v#1i+(w=77QTar{LIM`e2_A~nf5>Hr~C}b z0%d@ubFbaS^Lak!jAx8JPk;~Fd0)f3uNJGH5n0}I2lNl#5Wl^W;ip#v9kG9VC28k` z!%v9fkBT(kO=$+jK;<*T;m2LE$J;_LdA5K1C241FjNg>sX;wd7Krrurk|qD17vj!F zVZOym0F2A5DM@cteAG!9A~!UeK0oZcM~S?c!4EIR1Ds{1iC4l3^q zDV$Z^;>R@uBX%O6?kOJSTcyrj6TIrZy2tu7f5qv#CHJ>sJeUCeVgvO&hu$>i#pxP1 z%o5M8TRWz?(nIq68x9$!sR=S}qbjYS5)_0|mb2Fin}-quz*uqDO^61I@>e5=-TfWZ zL4EK7BSXfuPQ{C|=au$cEF1WB4LatP8MS2qg-UB~eb}?-hEhjTMWdQH+MeZj6Si9{b!@}fsaI%)ZHu_+{h+~7_^klVV#cz5(UToarJ}IqLX|z?Ph$Uz6 z6bes^&WbA6GS2;D#Xx?laT8=VJEKKnacgo>o;MoUv6Cgc&NOv&!&b!9SjwBqWCHw3>AWGx&GbEBmgL72CT8$~)of6RbGo%W&!ermUVTC_Ro4#`E2j4Y%w^L7<) z>OTio(3o8&sPcO+tjFNEEs-!^G&!S&Zu77qxi$|?t@Jds6SGr!v(d&_ttMBxWi0cy04)=)1uqaNj!f*zV0>vAWvc+o>=teimK!NTX5Ru2z|)aQs>bb-b~Gvm9kY!Lh>GIuE#Y@;L=8ri@K1N zxr5oLPY91JDq`Vv1aa@5Qp-JIR@J7eWl{|p+SY&&KZMT!(3*ssK#Cp3my3_hpMmjH zXO^*f2k+F*do{9qCmU&h@=>*(z|R1Mq>h4cdV+Q;=Ec2Xkg?jX&16-G($=~MwsBtP z+)7j5o2U3CL-q_pKG|+gMR5=vr4EG&7S6z*swL9SBS!{(v8*y^=MkXUB%15v1JFW1 zG`Pr*c4;%~Mxjf}nrkGw`noFW5bNZ~U3 zi(ujad^5~3iZES*{bfjd>3z(!In6QU15PoNSogZ=VHv%?EQd!kfZy5kssVu^^aEGH z1EnLTzg4H;w2hi<-@W!z?*}Wqr;Vo3}$KBg4Me`-a-(h8i&6&#qGcXH#iz`OV9Z zaIYF-0pj)O)}8hQd_JcAdnV6FE7>PUhb+w+p8vn#A{3ROjE`r*E|>bGJ9gpC*_U=5(+z5by(vT+mOelok>&KSW z=`qDBdqMe6&HqxxA=>uU32aE5Sjl%fhs?kVvv!xhx4GXoE$?|(Z{Oe4d4cI{Q+aY; z>G82uLVG~b1Se7dlE5E7R&z_AZxF|_vM67rdER7>A!g!A-@1}G`LR#e2YK>aJ(;_I zt#Vq|!l38(OVz$(9p_RjS`oe^=Xu?H6hO9>uFvOX+)-@v@O+vpE!r{di@#4$pvJfMj3;D#9g~WmKJ=TZucB0M3@i zF1H~}WOcfi&0}=tO0fNGW986^L&ny%D^yKB6Ek^v;8!vU?l&89$~-_v!t%`4&llkk z3UJ?@pYa%`tV*``b3b6O%_NLlR?6bZzX9_nHB2!uO=*CUsh$d3h6R^b-Ezq#B+7C!~;Z$>w$BvB`0B-1;zLa|E26 z8{n8lqlZzPKVU*zm0w6S_)94&ySW7)uZ=CbmrsS;Rgjm)l~#`h0-f$6yC?u%T90x;5A8Oj*H@moj# z*vF_ z>b3t+*lY*X(T!BmCqAxcDOhhq?0YOxH2eqzzZhvl80~{;TQQ7U#e|E6U1{7rgx4FBwNz zaxYZ=K3(czULx(IS*QfctTQ00=SF!xlk1;w2I9fQxgTR8amc5f(mc^w*cj@M-|p%W z9c0ySRO-fzyM=jWhC-n|3RUNaPf>*k9rS5&XWZ}M1{{Qwp1e=rC>=)Nyf%|wu-_~_ zAdKoRsDBa#Laxx*A2!vv;my@yYLu+XMDjGwSYdj~=VW1es^^#?4NQQX@fMjEr^hDa zEuHo_Bedl2{$7h~rt@Uns?~G)${_;0F<`ay!+T!=kkf1&UbOg-%jJ=E5(u??5Q4Bz zfZW2I{ZP2=X)E{rSB$Tleg{rJ{G=tX4xh-*f!>~&q~_aDxXLs`|^g#ZxjMs|xUx8!C`*s55Zc{Ju(h(&&w+ZFlbBDw_jo{xd1#( zDoNZ4Nqqk3az`ywMlD=?LaAI$%&3$%tcY`B&$ z{CXm2jbVaFc%t5$S6;@*Plo`{T(D3^%w2gW@7btRVzue#hzpqP&E$e?e>tfHR7|>6 z3jO6vyqadkBr4h?-)qb)a5chp^{|Z#)-b(2m5yk6CZ*f4d(Aj%#&u==gg-s^$B63z zoyQ%URxv!~6|?TCs{_p=OE**&^DM$Mh4}27>g|x~ISjezFg6Zn4_l<8f~u~BstN;b z6(M%ic`Yv8biS_+nK8ul7p?q74f;uzI9w%1Y^)o%uN2;Drghm#EFOy zV3?LLj}*Qe@AJ5y{wCkSDOmZ^cJxfb>S5b$bRjqikk&oJfSEQ1CCfyV#=p-Gw>!$`VI|CJQBi44rq zg7QQgMgM`yX)aqPDL}op5-=5_R1T*86=gvTE$v7o1V-ZMf7~nu<LG@S}O!gH7P|wNDG85djI4 zTVTSPTOl&sbaZFSy;ZZvO+!Q00S25^zvF|PeLaNq>sCUUsq#cNxEhuH@~jB-QCpH3 z(b0>KVpP3%?iT5%RiAPluT#0V-l8?WO&YX0y3;{_J#>RHxE;m)@+^W0;H36!iVX3L ziiGs63T&&;q657d1&1McI=rSC@C=LeIM9E%+;;Yi!`rzW6&GZvC?EPf`T~B_2>2sb zju~kU|0YnmXOckomFhP~zjP8G)^EQU4Lc5vd%IVLBuvU9OpD4>x|jB?gvlGRMB^jj z7NjMX{=pMq3}Y;RBk3(Zn0$*2tgBp$t%IJrSle8{00=hLmHoL*n7PThmhAL+b$7c( z`7Ne!R`y)lo{ML7(NLr1Yy=GIThd_7XnZd2F^nsN4^SF^X?@vAt(Ef8MJQvKY_v4g z^l^ygsq@!qtS~X9!*1e)O%B0*fqm1N_LHfK7)l(ebv;Noe!dtz2vu8%zPSJHL{EC8 zo3}(9Q2~=BDP^ByGdllvDmsrYL4?QFPz__{-q^FPTYp+QTE& z&6sKnO_MmR(g#?lky>!rMGeE(Y9MM;U|y#uB%*;1v&eVROYB4v|Mwt#X{Fa$vJu!= zv!g=uuQSGMU*92>vshCoqDt9o>2?>K>P>K~@R>Di|lD?w4 z-w@H9rQ6!_VsL7?VR~ow&c45g?UCB4^|0axGNNlPWSmnBl!kVc@MbouMc!FTLuA*! zoGeO~=+ls;2kkf7+M;X%olh{4d}he)zHMg+9)cfG?9$e8R)MM&9EWcltT|T>ZFHkQ z75uFP{2i)<&dvt?oDdozl(E(kmAVuy=MuzfK2y!;>|0N{+RnWiQUP+{h~XTMaxC^2 z-#9OYl7pxTD@LRx`&4JEb7Ey8gPiyDAAjGJSi&e=?yzAR6`@5dKGh3!GOnQ3(SKUTHioCUU4lr zN|!`KG>s(s{!JSsgt*{OZ)?;ju~jJko9?aXXa;`q6-wXCpdTJ94E$*K8?t?& z0~hZ+u(yDFnW4Y~oXNqQDOj5XA@%-L;Qp@jt|`n<(Z17{W&siL5Sn;0V1RN0UAX{S z{4GO~lS}w%$y4D>` ztW0}u@ij4ev5XI{@vjhKgy2#vCeuvOo$<`B0KLG&Z}cj%!o#Rp%FA@B16&8BHc%^4 z2UETuJZ*{Wn7J|2mfBTJ%^0aXcpl7Z?rk%?HnR`u^KA^y}m9^A;{dJ-X^d5E+jRZ{~BlB`cdeqlbR z0~d@ucNeXr%#+;TLi-FL-KuH5YB7=(CH+mfH0GW}4FHGK12yZpHU{LY2`*8m>ZNOt zF$D3$c9lukXla^FJf$Z?9;FLI&MG=>o_O2onkOf6oefV6-Q=`p>m`%CsTSCxPWn5T z)oAalVU+0lj8h-ub|sqDBNYlYZt`XGK#obaOYyApUJ#5}>O2#vn)E-l73r3PTIanC zVV4B&22Qu)z8A^)v2`d8FV8Dl@8M|U`s6N{@@<}y9B3GizhTquc*J|cRSkG8PkF06 z=2&LOe2Nb7v3qmR^7r*NV^jhB3Q*Hyryif{p`k%$d-IBCa9h^|JAy|IqqNSfK)x3l z`K{O#vy<_WD?E-$6%HdVx4(16%&?Cc(D+m(DuB(Y-rj7vl;VS4RTaZ?%$J-7f};xN z?S}a%0psf#w+iy4!=tFcNXxt}1I(7#z*Ws5HpS8~&mL$(+qJkX;(uuncf{ep-9?Nr zEmf6RHY zK;haczzfv(q$HE3!U?-318)D91$#M1tckE;y)#;%>0YUj6&7cliLv3FV6<*%gB4m7 zbTe7dhf`A_4r2UR$_}pL=f8SrmB)Da&sTWvy zoYD4sM}!8Ma3t%K1*Q^HPZCD(Rpf{L?aD==Qs;aDOLCYn7%BdbK({4knH0v}>b{os z=1P;V)Q*1)804$5-A9$qUtl6@+~KKoyL6KKaH)?D;`!9|EJ3h(^|CXqTU<2!}6KX!Ce$@% z21n;Pbe&(!VJ2^d(~;VF=&JmY*J5=A<;GW3u`}a*?H6>}Ml`auW&aS9g0kKqxNXr6 zl7QKaKrJs{G!OKDKaHbwNuUc#BA8ZLI<_v1`!vCWA|lLoC`81;5XCuH2wB8Ute01G z0p3b>HIhA-Dc*Tn;w5XgBJ(4kLN+}P^BOgh{Fj6;s^WhfEI8M<>8P3WW`AZpzIQ%* zUq9t%zE2CnK&uA?PmICo>=U=T<8iaH&^TkGff&W)cnQb@;lV{LX2o94(UNUpcO*B4 zQ?!ixCnZ~WrzZ&5(A{zpoCY(~IggH*2K_}{=G`cDCW)Gpp71x&`z>-Gok#|=jXOk# zF`lS(-5q$Z2lR4p8o9kSc*@;9c+A~FS@TFYhsPcho|rrIrtvjWd;DA7nggFAp1|LP zz~B2p#J*Azr~*^CgvJ0$GGDb3o-M{jXhDkoLlgy>w_u@RP>TBe z!+LMAm@|#wQ(VZ242sgSY>sUVt>mor52KBF`leNm(sa4$h$r_p)J1bSjFu@;Z$7&! zIZCBX~N)<#aW$A;(N83>%U=hl&n*EFUbk5OP5=GAirufqPu(R zMLAn)T}_mlUdw~`64F}TDeIX4~?${v>N4X() z`#8z@3iot9)%x3*srPwddZTWkAu_W1lN_B1`^`VZfLErGlBKf5Ff>3~JJX=C>RLa(jHxP*MlJ6`C&ns-oN%Kb@i zNr8fgjAD9V>A~e1w01+4@{<(`S#6i&)-w6lqlF4=(7~PT?B*HtWY2ZJdw=(DVR8qG z`xXGVZe{Y4idL#3>zb~?Iaf!=P2{uy#*yg0l%_z5y$@NrdA&JcQ%Tf>yD^W_e1?IQ z2OEuEYR+S#jdE^Us(~JL0f)6s<>5(fUua@Vt65C8a-J`{9@*(AyJgx$*~^1ap;ubw zTx65u2Zzx*MM}a*CW^VohziEZ5SBBYgY@1;ri%IBcE7aCidMWiJ(mi5$me9sQ;|lO zOQQ*vh1gbEx6m;lvo%{~$yuR}w5Gc6jHc2)^NCVMlXaH1-Jq>CEcZJb_m29ME>CKS zSCnZ+*j276wPaD%R@u7y4`f4>!pYn_8+(G~v-L{1*GwiXaYK19f{03@<*$7&C+cD) z{~%@tDzqtc@+HMxO_W{ro>ql6C;5Hwg4Q>?WZsobOE)XvIhKdkeLL(5n4=|Q`g$LJ zZ#h$Bu<>x2yzXSFU1k^Hnmf$4TPi2ZU5ko?y}NVFG^B5zD46P%diPc9sgX`*(l@-; z$Gs^k-BN%+LCW8&i)_Si{-7QFaY-Pi{p)c?{55+|eDzwaznRwL z2}Y~4wS5ZH3^5SE+OA-PmgVKzlYeSnPOep49GV;) zgCsv>Fyd*bS%nRKOch0a=s`p4Y)&>$l-oC7rwJaXoJe>O331{R^Q{N78)vQ4zcA&X z<=|WP;Ifvp0>sZhY`A3IqtLsg!4HSQx4h8Xl+e3n(A#k+y@aGw{LM11ga&;510we*h?m4td*dR>tvLPzJnv!BHOiSHL%smed$gA*;DLX;zplORIwb`ya4wO_9FdT`poYa?8&Z_JGaQd9wc2VQVNpK zubMD&wr^OdpP1ju!lWF6&^&4lDGLlYCHN%vIDyVwLC<9}7Ig>6W{OPYp&P_$2BXGp zgrWO4!3#*&Vv5NBkPzq8j0ZLI$uv#8kN21vo3j?A?bl&Hq;gi2|7BAxZv+5E|GJ_Iz@Ak-kU-ehrEw?s= zkU!N6op-HzH=hHRYf}blrxWmX){qp{hy)HCA(kP@AqF_h^Q}0aYG&0}OT(A$c8Z=3 z@3~ca?6x-=?WbdW-Q}w@a+iLat<=VAW4X7E=@<8u3flmF^Yq&wIIBt#poMN2f;wJl z;Qkx>J~veCnq|0!%Pp1)FTFjX!=uGoa=$tyY}GJ{D6x-uyP3EEMwfpgge@HCnIMj3 zK0EXV9**k|1o`ZS${#lt^My4)Gr#Uw<2HCJ2{DEJL287u9;-2l`GC;E5ZcX!mZer_ zAt@?qJN=+&sO#!xRu8E$unmjiUz~E5T0dNmGPL(S$(R%r+oTjP8wC}mM?R{|IuF}_ zv6AADhxQNd)2s};0yD#HJ+xj~2X^%U{4kSQQqXTN4u2-MSfuudv0tJC4Ccx>qvc;x ze(z{Hy^fJ*X$Tae}2IL$`)e8ybk+A|PDC5o;*pN&5 zUm^C%PG&{3v7w`zW^HFv)3uG+^=HG+uSEXe zX>Z0jnb+^P&$p1zovzm{m)Qrw(_Ej^uzwx5SjhDIf1v=3r?RR<)7ZcTxr9`UQ%z0#>^WD~5LzmK=r)H2@sU4Nk&;@oSenx9&d_==twfQ?K{rZ%0*4H4yrKzsDfNBdALL57NOqN?!ebG<5S{QH*yQB5}j4- z^%u!c=x~y)mrp8n&R~Er8JZBqToA9Aa|m94p}Swx>I*rh&TIkqKzcJ=;<9t9mH&d( zUdK(DG=nWkt^nxvd}-6lY2W3jFZ$S81K+aQ#%^oh=_oth3NHuwUqhXSqpnQ4qrGga zo8WnBT?*|6y^q=Efi<6uz4u4%$EvVtu{qs>jiP#{Qeo06E>oR9b$;7UM?J`A`Ws0Bv+?h#Dbz)gpIG?FHMjqmz^rrQaDeuA64XnL)uWabTfJvtq5Y z2?rMmg<>b+8fGkh#Jhdo(nWH&CAXS_Zz%dlWe}0 zAm-~7I zFDBv`!nYaK)1!xYxj^LVvLwn@=sqzG9se?_Vh1kI6W32|>Q5e>v-r#t1~~981$r?< zjMIe)FWAazBX`wVqs~EF)ke8yO5=9(;e!PYTvfu~-z3MR$dOXauWUfw;l7l_>?HeHypmUA<82 zDGfYyHL$l2{4L&Lq-9C5SuNFeXzJiI6xf=C|AQ5$!7h=E9f)uVAAtqO>1NjVC-jMv1zMG8c-f2U5?{Es#<#?APg%-c?~3xuAC>;D&}6m=DbP{8OF>BUd6Hjp^{## zy@h^Er-@Nsou-&!BxmIA2-?)xQ&Ka)K@%}RqiQQWB zyIYw|@b27RoMe{y1cR0s(7}No!INDdv4Cv~Z8n65jF8o&+{JSVWG^C)C9hHAj0Wb) zvh;(h=#yZu9)t_xi$T-kyWtqZCP^gnk~-O%&Mm8|bi!JbC(4I{IOug@vy*S@FuZ^k zq{>xXL#iCm&aFVY(V_MUn6~NW-htfUeB&@E48Ka`-)Gc1rCx&OVYk)vR-+~dFt@;H zR~tx>)%1g};}Q2K8aHy68L|}BM*@F0Mmnc2kE$}WW^M4x&^dHbe@3-(CecKV)ZPds z3KrZ`f-bj;c^E2`n&@a9WcF{FyX60B@UQdD*C zh%eBkT8!7DO^U(1!gG>_T+*!bP!C#lFzL8_L_1%W!_}cvp1LjAChfPVcmmaP5$f~@ zlV(%LZ}~#T%d!BE-f0MRjWQbAe>SAX;LGj-8c~=zt)1!kwi?(w5fO5by-H)Q-q8fB z=N?Z!IRyDoZr&LN^XEQK^$MDElCB>}f3(W{aOTc&dJ$mrG?r#;VWPZQ%8?i`1G-2#!?(D2t2c=i-3U)SCk z!MOZc{Ap+tNB>%1UZC(P<3pP34~t>hqwo)mbDR%$;k~BY4-QcORMa|G+~wh%Lefk$y1cZME)05&oScHkPC6+4#liit*JGkj=Jq8rf+1K7G6T-+40|M6si z+lso6VD?bI9%aY9foYIka;Y0mW2)6YU4vPqd)oC*kQ3+lcLv-SY7vqWjikpNMV4%! zp^Y+NM)KVt=62C5{_k+}{Si*oC7f)!g&Shm6xiwKd%8ki*`}MHKjG5*CKI*Cb$lb` zLaDP4*Ze*Q`<8KE2k_b@@^JVb!+$e{!s02UD_VBiu?jFU*ou6aNSqS<;I#C-+`-vQdZ_m| zj$-Y927~xj0)5O!(VW-2bC`30Ly9m(WySCJze^fZ@@5pH#Jf!tBO3F}#w0WxdH$)5 zS^RZFF1w#~^$P{oJ~k!tMva`LH$zWcpj23OMdBrQg!jwk;NB20xDMh&SMkujpJL(& zmOVZmV9_V&VMz{`-NW+y9b*K1HO|!CRq^~w1cvMr0LuiH-dDeHbAXe7MWruvkk6ki ze|lzsNRmGZ|B@T-(i`PZSIM5gwYT_Ono&98%1=v=OZT^k8~zy%;RBb?37-^m{*4%d z!6;^bWng5R;0HHyv(oiPCpwLv6OOP7Xx2`6Cjmxu@%&w!5_J_$95+_X$-T1=cZU*| z;jEvSf%nV<%G@msi)d{sYB?aqR}28F__D12Eqie#Fpta^scXu#u(?VmJ zh|6V>H#2w3&O6uIfSr{bnSQE_O;`W{3o(iUW5E<{kxW~RyF%* zmF??L`W8$cys`I!8V}~a`;-TheN()fCshh zWTpu(H_R+m1+Hvf4_75ugS-6^C~rc7XcXC{={AQ#%@0BgX?fBjmPOa7&YJp{?R2je z(E7ziZkpeP}-h^n3g z=PcvekCqjWT>DRI@oaL@n4&)BUoYqj z4IuWK3P~>>lh4>3%+7@G%9MdaU5KE1pTtk8A^~ zpLm#~8PHouXV8LN%>{wy?#SAaN4PlyQ9|)gW?y{-1OvX)6!^LJMq!v%wCLF~$zM`} zBB&D+aq$`&PQhR1DUwzP#w`P*^9q!L8y)G`rPQt%X6cem6<7|B=Q`2AWNyB5)F)|@ zX9|`EZ`6v1rL3`I;afLXes8hNA~Z7i*_PvPHUMhoxV-u6oLKjfrjO{3A5Hno_+hlQ z;0I;!R}foC3EdImJIw?wRC#?~seqKYM{Hsf8c%9~3#3_1F?KUjBBP44y`#qDL`SN9 zzM?P*VNK!p6TlIAfu$3O{PJvMkNqdqKGcFW|J3r_B|lS^^&F%7{aj<}#pJ{;J?OB# z2yr*27ewL(Q;t`rKt71Ar^MIag3U|A^O16~4Q#nr#9^m~t%>F6vMhtPJ7h;U^hXEz z!3(C~s5(gWZ{=0lI+S$X%cShsKOu3!9PMlTV#i64>53OGjW<}}H}P%5CO}yLKBPP3 zr-YD8#-*wyenrvJ%c1C=c=t&Bo6W&;j8cD?x9;w-zkt%L>hqk!MTy2mkv&Lr z+zzZzFJb*i}oikZz`{GAnGv``kV`_UGZu8 zAr%>XV2l9W%(bz*2u)48q>s22jALTu1r;8RdXO!=KzO(;-icGuB$w3%-AJ&QaRf^=Nk$;hBeo>z?LEhmV1SD)=?hd=hhMNW)5cOy#!Oc z5=KbRO8e%BJEaROsBC^-w+ns3i5!6OSI>_o=H!tX)U7o{KYE8-O}wR z6>Ybb^#aw2RR3V%T-d(|__N)7b0ha-f|_BJHR%C>XHcD>(M$@fo@SM7FWV5*=#bt` zKrF-o3gz!vZXs`Rsjjq@lLS;>MvZ9qo7Mh*NN|2oXy3o&!TmA82dap4=yy0lg0W8- zN_~Oozi_D1=3mc|sX)dXBkkpQ`TR2uPP(gIa&kZCNnz5GEPqSOO+t(?$N2dRhDKPx z&hf8<9-By)#U1=JeQIyA>+|;`{}5f{nBf=N84OZeQZcxT1+82kk`GebJouY;p~DAx zx%V9C{f1X-xXUMW%I)O~{=ns|Gzh9AD1{5u{dN9@@otZoFAu(?7Sr8}-MRyY`|)|6 zL%$plRzX#aZtG@3j%IaJc9Js|XL4XC4+SV4bcZr%P{k|pcvg*ay+T0@Y0$I^YR1Le z)pryY?2$AImlV`rQ^dp%bhPvxde9Gb^@@0tkQ?@jo{a7}K*^_J=lx0b1KZaH-YTykugF1=?-P!{s_#b$ z101oUUP2fa?pq|F2dUhf0rtfk1|k$#Z%=e+`n3#DtD1krv0I8)I^&v_$OGxmk`?>! ztjTyOgZp0~y_xmJT=!*7ti1!Al67tiTm7sZ=opzkD@bpnjvfgf@Fqsw15jSE!+%`T z|4aA2xy_?15W<>qc;t)I)TScGKt;TBDNv5aZ)wlf;|7otZ#7b@m~_J>Pbq-28`|h= z2&c)^LK;&#U}6aInyo8YW2fA%AyC9`C{k(gQEZySud?-DlUi@+Ln~Ex!;A?Qz>u07 zbR7}kn1m5?xxsKcCfJPXW-^q^NzlWpOhAgZV0G~>m0&m+>e!=0X8F8>*GiUABIRGq z64~Ig`gXB}=b$C>Vc{X+hhbpj;8g-XMPM2gxH~hY#Q{{gRB}~(9K_OPnSJoUVvsBJ z!I(8RGl$SPXrnTHNcTbt%sbSXN<>9&5L^+P6h}aNMtNW7$up~tP5GuMS(0>8`m5$@|#B%W}sN)QW#%r|TWl#R@ zWcLnIqQmn_8aamVzw8ClMZhRy;{=}i7Fn1?ObImNn|y&&IKCjSTSL|kzppui%O97Y zS+rK)?Z~0h(x~^WdN`i3gSc=ss?OdSl*?!##OIBw3S6=!Q5NWA$USliTFx@g=oDhT z^onX1VyVyASkHP`(;*V+iv26Qs2!vkd5D#@bDedfIWjJwgxUkv90Ce1nPur}Qc_GE z5aBde{91|<9bMz+Wr8EeQcMD{Fc zhLjIcxGFm_QWRy0EGb(=X-u~4k$vq`;|uv;^DX6n=b7i8bMEh)`_6mc_j%4U_kE9= zn!8C<42C>=E)wfBHS-=zaUc7Xlh+j>{fMG#9-{k?OwUA2?{V zsObP4UtD$rP-dwb8D!R$sS`rwEYcG9= zi!LP@3_VC76gja)uF-Bw1YEXjEfIX#-JOwfG{}Rpyz_-ilcvj-D4!D@!|Ute&|Poi z9xH1-IU|r z;}+Kms#AB874av^zOH3UJqnPU7qPBrllSlzJy+~jl+$tCWeriN|InqwPDV>+_=`dvjd?CDp}fe;@B6Tihr=M;4W&!eqg~nh&bYAvPmO9D0Y<}PnYMjMU!ivloY_X zUy9mg&I-lo9-{SECr;McxJYa}@-r!5wE6pD2vdeS798|y#f|}A%DrrgOpQrB=hmv} z6C!2e8d&sxRJbuPIhg%nf*09kzB@km>aE{pCk#ui_F5UtnOBI$C_QE z;y1!P(2XAo2H%AFnP{FLF#k*3=1^V>S?p1_X8G_3z7F|ysiB`|UhdxTaqbm$UonNL}{Qnxx1bT4#vXKPty9PmH5o9eI8uO@%cq}kFQ)W@uw&1msBuYISN%Vl#m zOn&-ajlgYm`7okqKs2uQ^>7x}Cw{Z`dQ6yAv-p|;Q=ZF>vsj+?Vo~hv9{rBVO6gI~ z^%J=oa?Xr$Yik8@a&en3haFS>IUNyGQjfPS`Jtsfju+5PTJGwD8d|IOy+zc#uz zK7IYX(fq0H#nQ&7&%N3n->*e=bX$I@OT|b=C{*jiBGw;Al}YN<_~_PHr==DioXd4g zzY-kb{Rm%D?}@pQTIcgi;QOJ}~AK?)A( zmz=dA1Q~SRv3TAyZOw_Bs&3+aI-umDuIQTHmUaOX(qf^kY;jbnd~k8LvsBS5(``=E zr`+p#Kh9#gaoy%-b#uf>R_CzPIg$cbzq;CwGOOE7!V{>uWqMY~vgb)wnKr##QZC zlMV!Bqi+RcB(XIX3@w`mry$#ki0C_$j*g!+-u7{@*>r>~@>Yg*IG8{%wN3hyiI*cx zznwRmYh9SvYulzB3^Q!U%(!C{q(_%WI~4KPtlg&1=G%>RyY!L_>H5;vas7Ys;?HOq zbZ*j>i>(>)Ho44OHlQayZplToZ&t^RcMvvZ@Zz5-iZk-2JSVg8W~~Bl+iYCC<&&n0 zKNAP&HeM{fy`GWzF@bl~>Zb8Bi&eVj9k0FvD4(-506kwl{MMz5?jZwtI=EOI=2S?_ ztuoweJ*BHH&kE0XCLaCn8<=&yX~Kkec(XLI>DlnGkL-_2m*8{0D>;f|%|}W&nk0J( zIUx@Z-QqchYaMsrj^R*>CYTQe-pGzO=h9P>T9_LMo}yo0_C0>|UVX{Tvbx<({G+_E z+mv*~8@(*}HzyozcJRe=x&DfYtUE&lbkBp}l9FeX;GG@wDGv`mwUele3-4s2tQzW1 zEq71N$zc(cDr{3dd3+RY=#>!IX%<&U@RYRv=#Jy?Cw_b9cglFIQEX(F{Gy>OHJ~Xu zydW}g7q&e7qj^N5VTC6yDR38lf{(6Etk!Ait4KXJX0u=WSz--YpsUK*siE&e{%$dvp$8>=!Vt;m z3Our_qOXFdHJqHEIApiQYI|Shy@dK$)Me(0nBy!o0tJ?QpR0bZm4&3@KOQwekol|4 zkUgXYd?sCH%sTS=fPLTX4-zD1X5lE|uym0jTywZ_Z#N8g@|7^LvyPp%mBTOpNp!DC zRg}bilrDpwJy9q3`R!JAvJ1Nx!FVg(4-`$A$yz_fwXAfYV%?49_0XnGLF|W&SIVuH zKc(z--Y66Mkg8sy)+Da^2hM_;I$Y{X8@Ws?bNqvad_#qchMn#AKPV&05C<5vl{PFF z#+cEkT$#7cR_?&R{qHQLyv8A}GZVax^H=L43)dr*4%h3Vw$<0O2QW3O4XkS;2 zu;OLDDJ3E%TfONq7A=q;7w3XEzo@`vd*Gl>5eiJSLRZTepio@06(-_PMuYg0q%DmxihFyzy)3AfWCX!g+OOFA9op7A1@FK=V$)%i-#p65u*Ev$8Z5qdjwgj z@`-n^>=P379_9gG`f-A5Gm3yPElBF%f>b29HOdOog|IS1-qeE_oy173+9Dsq2yV`> zqNtUkpkgkAP&T0TF9n^6(f&+SaCuf4wZH1iFNzp_5b;k97)jLr!XCdvA*>K&0^AU! zvMlqOK!^~vh4U$bpf1$Hw0R^DDM2-@r6a-dc~;cE?LfbRLG99%`Ul+G9V7&0BUcwsTEYKCYBurL+|tBRi;Az5cK=95Se{Y`AI7DY5{}#@AW)H2=0R}YyA_6 z0Yop#1J+hl717AN_hV4hyq5+BTZPu09N*_hYYSyshN%6?*Drz?J#&tlib*cX1I{i` zP(?6l2?GRN04Lwcfy0Ze;N}t%@bQLJ6ez#U3Z5QCp#B?WP$1!7!Jv+M69h`bMZx)D z3?Lp1X2YdG%@GXX9}N18r~!GwU><@Olwn4KBajsn3aRRl$O>AHB7xy6V8EyY5O5Vd ztBwMfMo$1FJXM^XMuCoFNPy=CqzZ%OD2BaJ1bQ9$eTV_EAZhFZ@E{&KNq{cnDnLde z6@NLd3#=wnMTrShARnZP*%Nv|S0+_tnA8Q{=0Kt(=&8gFCQV|1o_wn6dz=+CnP3I= z)PBFycp>%TRW&w2JtA3~xW7^(TU>#->$@TL?p34@M0e> zCcZMNi9DqbSU!V9G^jBx50tln0n_F{Ll>yvDhry9L7naK`<*A=g92YXfT|Cmq^H_m zze)W6eE#=Y5TpO--8Z{G4lAJcrt`lCF?wh}@EFd=ZDvFVtyzM>l%dZ8G?w|vd)og1 D86?w= diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e5face9e..ae04661e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Apr 06 16:48:19 CEST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip diff --git a/gradlew b/gradlew index fbd7c515..a69d9cb6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,101 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a7..53a6b238 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +71,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/java/com/michelin/ns4kafka/Application.java b/src/main/java/com/michelin/ns4kafka/Application.java index eaed6e80..03d1d8dd 100644 --- a/src/main/java/com/michelin/ns4kafka/Application.java +++ b/src/main/java/com/michelin/ns4kafka/Application.java @@ -2,7 +2,7 @@ import io.micronaut.openapi.annotation.OpenAPIInclude; import io.micronaut.runtime.Micronaut; -import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; diff --git a/src/main/java/com/michelin/ns4kafka/config/KafkaAsyncExecutorConfig.java b/src/main/java/com/michelin/ns4kafka/config/KafkaAsyncExecutorConfig.java index a1440630..ee8ca12f 100644 --- a/src/main/java/com/michelin/ns4kafka/config/KafkaAsyncExecutorConfig.java +++ b/src/main/java/com/michelin/ns4kafka/config/KafkaAsyncExecutorConfig.java @@ -3,6 +3,7 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.context.annotation.EachProperty; import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Property; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.convert.format.MapFormat; import lombok.Getter; @@ -16,75 +17,22 @@ @Setter @EachProperty("ns4kafka.managed-clusters") public class KafkaAsyncExecutorConfig { - /** - * Cluster name - */ private String name; - - /** - * Run topics synchronization ? - */ - boolean manageTopics; - - /** - * Run ACLs synchronization ? - */ - boolean manageAcls; - - /** - * Drop unsynchronized ACLs ? - */ - boolean dropUnsyncAcls = true; - - /** - * Run users synchronization ? - */ - boolean manageUsers; - - /** - * Run connectors synchronization ? - */ - boolean manageConnectors; - - /** - * Kafka cluster provider - */ - KafkaProvider provider; - - /** - * Kafka cluster configuration - */ - Properties config; - - /** - * Kafka Connects configuration - */ - @MapFormat(transformation = MapFormat.MapTransformation.FLAT) - Map connects; - - /** - * Schema registry configuration - */ - RegistryConfig schemaRegistry; - - /** - * Admin client service - */ + private boolean manageTopics; + private boolean manageAcls; + private boolean dropUnsyncAcls = true; + private boolean manageUsers; + private boolean manageConnectors; + private KafkaProvider provider; + private Properties config; + private Map connects; + private RegistryConfig schemaRegistry; private Admin adminClient = null; - /** - * Constructor - * @param name The cluster name - */ public KafkaAsyncExecutorConfig(@Parameter String name) { this.name = name; } - /** - * Constructor - * @param name The cluster name - * @param provider The kafka provider - */ public KafkaAsyncExecutorConfig(@Parameter String name, @Parameter KafkaProvider provider) { this.name = name; this.provider = provider; @@ -94,19 +42,8 @@ public KafkaAsyncExecutorConfig(@Parameter String name, @Parameter KafkaProvider @Setter @Introspected public static class ConnectConfig { - /** - * Kafka Connect URL - */ String url; - - /** - * Kafka Connect username - */ String basicAuthUsername; - - /** - * Kafka Connect password - */ String basicAuthPassword; } @@ -114,25 +51,11 @@ public static class ConnectConfig { @Setter @ConfigurationProperties("schema-registry") public static class RegistryConfig { - /** - * Schema Registry URL - */ String url; - - /** - * Schema Registry username - */ String basicAuthUsername; - - /** - * Schema Registry password - */ String basicAuthPassword; } - /** - * Kafka cluster provider type - */ public enum KafkaProvider { SELF_MANAGED, CONFLUENT_CLOUD diff --git a/src/main/java/com/michelin/ns4kafka/config/KafkaStoreConfig.java b/src/main/java/com/michelin/ns4kafka/config/KafkaStoreConfig.java index 5b522550..0d23f3b9 100644 --- a/src/main/java/com/michelin/ns4kafka/config/KafkaStoreConfig.java +++ b/src/main/java/com/michelin/ns4kafka/config/KafkaStoreConfig.java @@ -2,39 +2,18 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.core.convert.format.MapFormat; +import lombok.Getter; +import lombok.Setter; import java.util.Map; +@Getter +@Setter @ConfigurationProperties("ns4kafka.store.kafka.topics") public class KafkaStoreConfig { - - String prefix; + private String prefix; private int replicationFactor; @MapFormat(transformation = MapFormat.MapTransformation.FLAT) - Map props; - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public Map getProperties() { - return props; - } - - public void setProperties(Map properties) { - this.props = properties; - } - - public int getReplicationFactor() { - return replicationFactor; - } - - public void setReplicationFactor(int replicationFactor) { - this.replicationFactor = replicationFactor; - } + private Map props; } diff --git a/src/main/java/com/michelin/ns4kafka/controllers/AkhqClaimProviderController.java b/src/main/java/com/michelin/ns4kafka/controllers/AkhqClaimProviderController.java index 25773434..0c7cdcb1 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/AkhqClaimProviderController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/AkhqClaimProviderController.java @@ -9,11 +9,11 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; import io.micronaut.security.rules.SecurityRule; +import jakarta.inject.Inject; import lombok.Builder; import lombok.Getter; import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; @@ -26,15 +26,23 @@ @Controller("/akhq-claim") public class AkhqClaimProviderController { private static final List EMPTY_REGEXP = List.of("^none$"); + private static final List ADMIN_REGEXP = List.of(".*"); + @Inject AkhqClaimProviderControllerConfig config; + @Inject AccessControlEntryService accessControlEntryService; + @Inject NamespaceService namespaceService; - // For AKHQ up to 0.19 + /** + * Generate AKHQ claims for AKHQ v0.19 and prior + * @param request The AKHQ request + * @return The AKHQ claims + */ @Post public AKHQClaimResponse generateClaim(@Valid @Body AKHQClaimRequest request) { if (request == null) { @@ -49,11 +57,8 @@ public AKHQClaimResponse generateClaim(@Valid @Body AKHQClaimRequest request) { List relatedACL = namespaceService.listAll() .stream() - // keep namespaces with correct label .filter(namespace -> namespace.getMetadata().getLabels() != null && - groups.contains(namespace.getMetadata().getLabels().getOrDefault(config.getGroupLabel(), "_")) - ) - // find all ACL associated to these namespaces + groups.contains(namespace.getMetadata().getLabels().getOrDefault(config.getGroupLabel(), "_"))) .flatMap(namespace -> accessControlEntryService.findAllGrantedToNamespace(namespace).stream()) .collect(Collectors.toList()); @@ -71,7 +76,12 @@ public AKHQClaimResponse generateClaim(@Valid @Body AKHQClaimRequest request) { ) .build(); } - // For AKHQ 0.20.0 and later + + /** + * Generate AKHQ claims for AKHQ v0.20 and later + * @param request The AKHQ request + * @return The AKHQ claims + */ @Post("/v2") public AKHQClaimResponseV2 generateClaimV2(@Valid @Body AKHQClaimRequest request) { if (request == null) { @@ -86,11 +96,8 @@ public AKHQClaimResponseV2 generateClaimV2(@Valid @Body AKHQClaimRequest request List relatedACL = namespaceService.listAll() .stream() - // keep namespaces with correct label .filter(namespace -> namespace.getMetadata().getLabels() != null && - groups.contains(namespace.getMetadata().getLabels().getOrDefault(config.getGroupLabel(), "_")) - ) - // find all ACL associated to these namespaces + groups.contains(namespace.getMetadata().getLabels().getOrDefault(config.getGroupLabel(), "_"))) .flatMap(namespace -> accessControlEntryService.findAllGrantedToNamespace(namespace).stream()) .collect(Collectors.toList()); @@ -105,6 +112,12 @@ public AKHQClaimResponseV2 generateClaimV2(@Valid @Body AKHQClaimRequest request .build(); } + /** + * Compute AKHQ regexes from given ACLs + * @param acls The ACLs + * @param resourceType The resource type + * @return A list of regex + */ public List computeAllowedRegexListForResourceType(List acls, AccessControlEntry.ResourceType resourceType) { List allowedRegex = acls.stream() .filter(accessControlEntry -> accessControlEntry.getSpec().getResourceType() == resourceType) @@ -117,7 +130,7 @@ public List computeAllowedRegexListForResourceType(List list(@Nullable Authentication authentication) { List authorizedResources = roleBindings.stream() .flatMap(roleBinding -> roleBinding.getSpec().getRole().getResourceTypes().stream()) .distinct() - .collect(Collectors.toList()); + .toList(); return all.stream() .filter(resourceDefinition -> authorizedResources.contains(resourceDefinition.getPath())) - .collect(Collectors.toList()); + .toList(); } @Introspected diff --git a/src/main/java/com/michelin/ns4kafka/controllers/ConnectClusterController.java b/src/main/java/com/michelin/ns4kafka/controllers/ConnectClusterController.java index d1dd7e68..d63d7f69 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/ConnectClusterController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/ConnectClusterController.java @@ -14,8 +14,8 @@ import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.ArrayList; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/ConnectorController.java b/src/main/java/com/michelin/ns4kafka/controllers/ConnectorController.java index bce39b9c..a1a73eb8 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/ConnectorController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/ConnectorController.java @@ -1,9 +1,9 @@ package com.michelin.ns4kafka.controllers; import com.michelin.ns4kafka.controllers.generic.NamespacedResourceController; +import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.models.connector.ChangeConnectorState; import com.michelin.ns4kafka.models.connector.Connector; -import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.services.ConnectorService; import com.michelin.ns4kafka.services.ResourceQuotaService; import com.michelin.ns4kafka.utils.enums.ApplyStatus; @@ -14,10 +14,10 @@ import io.micronaut.http.annotation.*; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Single; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Date; @@ -29,20 +29,11 @@ @Controller(value = "/api/namespaces/{namespace}/connectors") @ExecuteOn(TaskExecutors.IO) public class ConnectorController extends NamespacedResourceController { - /** - * Message threw when namespace is not owner of the given connector - */ private static final String NAMESPACE_NOT_OWNER = "Namespace not owner of this connector %s."; - /** - * Connector service - */ @Inject ConnectorService connectorService; - /** - * The resource quota service - */ @Inject ResourceQuotaService resourceQuotaService; @@ -270,7 +261,7 @@ public Single> importResources(String namespace, @QueryValue(def sendEventLog(connector.getKind(), connector.getMetadata(), ApplyStatus.created, null, connector.getSpec()); return connectorService.createOrUpdate(connector); }) - .collect(Collectors.toList()); + .toList(); }); } } diff --git a/src/main/java/com/michelin/ns4kafka/controllers/ConsumerGroupController.java b/src/main/java/com/michelin/ns4kafka/controllers/ConsumerGroupController.java index a907945d..1611a892 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/ConsumerGroupController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/ConsumerGroupController.java @@ -1,8 +1,8 @@ package com.michelin.ns4kafka.controllers; import com.michelin.ns4kafka.controllers.generic.NamespacedResourceController; -import com.michelin.ns4kafka.models.consumer.group.ConsumerGroupResetOffsets; import com.michelin.ns4kafka.models.Namespace; +import com.michelin.ns4kafka.models.consumer.group.ConsumerGroupResetOffsets; import com.michelin.ns4kafka.models.consumer.group.ConsumerGroupResetOffsetsResponse; import com.michelin.ns4kafka.services.ConsumerGroupService; import com.michelin.ns4kafka.utils.enums.ApplyStatus; @@ -12,21 +12,20 @@ import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.QueryValue; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import org.apache.kafka.common.TopicPartition; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; -import java.util.*; +import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @Tag(name = "Consumer Groups") @Controller("/api/namespaces/{namespace}/consumer-groups") public class ConsumerGroupController extends NamespacedResourceController { - /** - * The consumer group service - */ @Inject ConsumerGroupService consumerGroupService; @@ -96,7 +95,7 @@ public List resetOffsets(String namespace, St .consumerGroup(consumerGroup) .build()) .build()) - .collect(Collectors.toList()); + .toList(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } diff --git a/src/main/java/com/michelin/ns4kafka/controllers/ExceptionHandlerController.java b/src/main/java/com/michelin/ns4kafka/controllers/ExceptionHandlerController.java index 7ab1f333..b666818b 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/ExceptionHandlerController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/ExceptionHandlerController.java @@ -50,7 +50,7 @@ public HttpResponse error(HttpRequest request, ConstraintViolationExc .message("Invalid Resource") .reason(StatusReason.Invalid) .details(StatusDetails.builder() - .causes(exception.getConstraintViolations().stream().map(this::formatViolation).collect(Collectors.toList())) + .causes(exception.getConstraintViolations().stream().map(this::formatViolation).toList()) .build()) .code(HttpStatus.UNPROCESSABLE_ENTITY.getCode()) .build(); diff --git a/src/main/java/com/michelin/ns4kafka/controllers/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controllers/NamespaceController.java index 1c3e02d6..d881d594 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/NamespaceController.java @@ -9,9 +9,9 @@ import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.*; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.ArrayList; @@ -101,7 +101,7 @@ public HttpResponse delete(String namespace, @QueryValue(defaultValue = "fals if (!namespaceResources.isEmpty()) { var validationErrors = namespaceResources.stream() .map(s -> "Namespace resource must be deleted first :" + s) - .collect(Collectors.toList()); + .toList(); throw new ResourceValidationException(validationErrors, "Namespace", namespace); } diff --git a/src/main/java/com/michelin/ns4kafka/controllers/RoleBindingController.java b/src/main/java/com/michelin/ns4kafka/controllers/RoleBindingController.java index 0b363290..09bf5fd1 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/RoleBindingController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/RoleBindingController.java @@ -1,6 +1,5 @@ package com.michelin.ns4kafka.controllers; - import com.michelin.ns4kafka.controllers.generic.NamespacedResourceController; import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.models.RoleBinding; @@ -13,8 +12,8 @@ import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Date; @@ -25,9 +24,6 @@ @Controller(value = "/api/namespaces/{namespace}/role-bindings") @ExecuteOn(TaskExecutors.IO) public class RoleBindingController extends NamespacedResourceController { - /** - * The role binding service - */ @Inject RoleBindingService roleBindingService; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/SchemaController.java b/src/main/java/com/michelin/ns4kafka/controllers/SchemaController.java index 1753b58d..b646a632 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/SchemaController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/SchemaController.java @@ -14,11 +14,11 @@ import io.micronaut.http.annotation.*; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Date; @@ -29,9 +29,6 @@ @Controller(value = "/api/namespaces/{namespace}/schemas") @ExecuteOn(TaskExecutors.IO) public class SchemaController extends NamespacedResourceController { - /** - * The schema service - */ @Inject SchemaService schemaService; @@ -103,7 +100,7 @@ public Single> apply(String namespace, @Valid @Body Schema .getLatestSubject(ns, schema.getMetadata().getName()) .map(Optional::of) .defaultIfEmpty(Optional.empty()) - .flatMapSingle(latestSubjectOptional -> schemaService + .flatMap(latestSubjectOptional -> schemaService .register(ns, schema) .map(id -> { ApplyStatus status; @@ -150,7 +147,7 @@ public Single> deleteSubject(String namespace, @PathVariable return schemaService.getLatestSubject(ns, subject) .map(Optional::of) .defaultIfEmpty(Optional.empty()) - .flatMapSingle(latestSubjectOptional -> { + .flatMap(latestSubjectOptional -> { if (latestSubjectOptional.isEmpty()) { return Single.just(HttpResponse.notFound()); } @@ -191,7 +188,7 @@ public Single> config(String namespace, @ return schemaService.getLatestSubject(ns, subject) .map(Optional::of) .defaultIfEmpty(Optional.empty()) - .flatMapSingle(latestSubjectOptional -> { + .flatMap(latestSubjectOptional -> { if (latestSubjectOptional.isEmpty()) { return Single.just(HttpResponse.notFound()); } diff --git a/src/main/java/com/michelin/ns4kafka/controllers/StreamController.java b/src/main/java/com/michelin/ns4kafka/controllers/StreamController.java index bb7027f3..ca55ed5f 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/StreamController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/StreamController.java @@ -10,8 +10,8 @@ import io.micronaut.http.HttpStatus; import io.micronaut.http.annotation.*; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Date; @@ -21,9 +21,6 @@ @Tag(name = "Streams") @Controller(value = "/api/namespaces/{namespace}/streams") public class StreamController extends NamespacedResourceController { - /** - * The Kafka Streams service - */ @Inject StreamService streamService; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/UserController.java b/src/main/java/com/michelin/ns4kafka/controllers/UserController.java index 4aebceb3..5d96afd1 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/UserController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/UserController.java @@ -13,8 +13,8 @@ import io.micronaut.http.annotation.Post; import io.micronaut.inject.qualifiers.Qualifiers; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import java.time.Instant; import java.util.Date; import java.util.List; @@ -22,7 +22,6 @@ @Tag(name = "Users") @Controller(value = "/api/namespaces/{namespace}/users") public class UserController extends NamespacedResourceController { - @Inject ApplicationContext applicationContext; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListController.java b/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListController.java index 01c3e625..079bccf4 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListController.java @@ -14,8 +14,8 @@ import io.micronaut.security.authentication.Authentication; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Comparator; @@ -30,15 +30,9 @@ description = "APIs to handle cross namespace ACL") @Controller("/api/namespaces/{namespace}/acls") public class AccessControlListController extends NamespacedResourceController { - /** - * The namespace service - */ @Inject NamespaceService namespaceService; - /** - * The ACL service - */ @Inject AccessControlEntryService accessControlEntryService; @@ -61,7 +55,7 @@ public List list(String namespace, Optional limit) return accessControlEntryService.findAllGrantedToNamespace(ns) .stream() .sorted(Comparator.comparing(o -> o.getMetadata().getNamespace())) - .collect(Collectors.toList()); + .toList(); case GRANTOR: return accessControlEntryService.findAllForCluster(ns.getMetadata().getCluster()) .stream() @@ -70,7 +64,7 @@ public List list(String namespace, Optional limit) // without the granted to me .filter(accessControlEntry -> !accessControlEntry.getSpec().getGrantedTo().equals(namespace)) .sorted(Comparator.comparing(o -> o.getSpec().getGrantedTo())) - .collect(Collectors.toList()); + .toList(); case ALL: default: return accessControlEntryService.findAllForCluster(ns.getMetadata().getCluster()) @@ -81,9 +75,8 @@ public List list(String namespace, Optional limit) || accessControlEntry.getSpec().getGrantedTo().equals(PUBLIC_GRANTED_TO) ) .sorted(Comparator.comparing(o -> o.getMetadata().getNamespace())) - .collect(Collectors.toList()); + .toList(); } - } /** diff --git a/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListNonNamespacedController.java b/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListNonNamespacedController.java index 7872ec16..edb3ea0a 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListNonNamespacedController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/acl/AccessControlListNonNamespacedController.java @@ -7,9 +7,9 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import java.util.List; @Tag(name = "ACLs resource") @@ -31,4 +31,4 @@ public class AccessControlListNonNamespacedController extends NonNamespacedResou public List listAll() { return accessControlEntryService.findAll(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/michelin/ns4kafka/controllers/generic/NamespacedResourceController.java b/src/main/java/com/michelin/ns4kafka/controllers/generic/NamespacedResourceController.java index 92f27a8b..2bdfea16 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/generic/NamespacedResourceController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/generic/NamespacedResourceController.java @@ -2,12 +2,8 @@ import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.services.NamespaceService; +import jakarta.inject.Inject; -import javax.inject.Inject; - -/** - * Base Controller for all Namespaced resources - */ public abstract class NamespacedResourceController extends ResourceController { @Inject private NamespaceService namespaceService; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/generic/ResourceController.java b/src/main/java/com/michelin/ns4kafka/controllers/generic/ResourceController.java index 7aa24eb7..ab08d8e8 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/generic/ResourceController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/generic/ResourceController.java @@ -7,8 +7,8 @@ import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.http.HttpResponse; import io.micronaut.security.utils.SecurityService; +import jakarta.inject.Inject; -import javax.inject.Inject; import java.time.Instant; import java.util.Date; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaController.java b/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaController.java index 95f0d5e9..ac50f826 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaController.java @@ -13,8 +13,8 @@ import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Date; diff --git a/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaNonNamespacedController.java b/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaNonNamespacedController.java index 9d889536..9aa5216c 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaNonNamespacedController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/quota/ResourceQuotaNonNamespacedController.java @@ -7,9 +7,9 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import java.util.List; @Tag(name = "Resource Quotas") diff --git a/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicController.java b/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicController.java index 1cdb4b5e..6e7f5dcc 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicController.java @@ -12,9 +12,9 @@ import io.micronaut.http.HttpStatus; import io.micronaut.http.annotation.*; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import org.apache.kafka.common.TopicPartition; -import javax.inject.Inject; import javax.validation.Valid; import java.time.Instant; import java.util.Date; @@ -84,7 +84,7 @@ public HttpResponse apply(String namespace, @Valid @Body Topic topic, @Qu if (!collidingTopics.isEmpty()) { validationErrors.addAll(collidingTopics.stream() .map(collidingTopic -> String.format("Topic %s collides with existing topics: %s.", topic.getMetadata().getName(), collidingTopic)) - .collect(Collectors.toList())); + .toList()); } } else { validationErrors.addAll(topicService.validateTopicUpdate(ns, existingTopic.get(), topic)); @@ -191,7 +191,7 @@ public List importResources(String namespace, @QueryValue(defaultValue = sendEventLog("Topic", topic.getMetadata(), ApplyStatus.created, null, topic.getSpec()); return topicService.create(topic); }) - .collect(Collectors.toList()); + .toList(); } /** diff --git a/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicNonNamespacedController.java b/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicNonNamespacedController.java index 641540b3..538f5781 100644 --- a/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicNonNamespacedController.java +++ b/src/main/java/com/michelin/ns4kafka/controllers/topic/TopicNonNamespacedController.java @@ -7,9 +7,9 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import java.util.List; @Tag(name = "Topics") diff --git a/src/main/java/com/michelin/ns4kafka/logs/ConsoleLogListener.java b/src/main/java/com/michelin/ns4kafka/logs/ConsoleLogListener.java index fa516b7f..f3654627 100644 --- a/src/main/java/com/michelin/ns4kafka/logs/ConsoleLogListener.java +++ b/src/main/java/com/michelin/ns4kafka/logs/ConsoleLogListener.java @@ -4,10 +4,9 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.context.event.ApplicationEventListener; import io.micronaut.core.util.StringUtils; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import javax.inject.Singleton; - @Slf4j @Singleton @Requires(property = "ns4kafka.log.console.enabled", notEquals = StringUtils.FALSE) diff --git a/src/main/java/com/michelin/ns4kafka/logs/KafkaLogListener.java b/src/main/java/com/michelin/ns4kafka/logs/KafkaLogListener.java index af706d75..cf21aad9 100644 --- a/src/main/java/com/michelin/ns4kafka/logs/KafkaLogListener.java +++ b/src/main/java/com/michelin/ns4kafka/logs/KafkaLogListener.java @@ -8,14 +8,12 @@ import io.micronaut.context.event.ApplicationEventListener; import io.micronaut.core.util.StringUtils; import io.micronaut.scheduling.annotation.Async; - -import javax.inject.Inject; -import javax.inject.Singleton; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; @Singleton @Requires(property = "ns4kafka.log.kafka.enabled", value = StringUtils.TRUE) public class KafkaLogListener implements ApplicationEventListener { - @Inject KafkaLogProducer kafkaProducer; @@ -32,4 +30,4 @@ interface KafkaLogProducer { @Topic(value = "${ns4kafka.log.kafka.topic}") void sendAuditLog(@KafkaKey String namespace, AuditLog log); -} \ No newline at end of file +} diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/DelayStartupListener.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/DelayStartupListener.java index ffb2af53..dd1eddb0 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/DelayStartupListener.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/DelayStartupListener.java @@ -2,9 +2,9 @@ import io.micronaut.context.event.ApplicationEventListener; import io.micronaut.context.event.StartupEvent; +import jakarta.inject.Inject; import lombok.extern.slf4j.Slf4j; -import javax.inject.Inject; import java.util.List; @Slf4j diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaAccessControlEntryRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaAccessControlEntryRepository.java index c495a700..00b2c56f 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaAccessControlEntryRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaAccessControlEntryRepository.java @@ -4,10 +4,10 @@ import com.michelin.ns4kafka.repositories.AccessControlEntryRepository; import io.micronaut.configuration.kafka.annotation.*; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.Collection; import java.util.Optional; diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectClusterRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectClusterRepository.java index decb77a5..b35302fa 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectClusterRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectClusterRepository.java @@ -4,10 +4,10 @@ import com.michelin.ns4kafka.repositories.ConnectClusterRepository; import io.micronaut.configuration.kafka.annotation.*; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +33,7 @@ public List findAll() { public List findAllForCluster(String cluster) { return getKafkaStore().values().stream() .filter(connectCluster -> connectCluster.getMetadata().getCluster().equals(cluster)) - .collect(Collectors.toList()); + .toList(); } @Override diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectorRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectorRepository.java index ff87a2fa..750bcfb7 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectorRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaConnectorRepository.java @@ -4,10 +4,10 @@ import com.michelin.ns4kafka.repositories.ConnectorRepository; import io.micronaut.configuration.kafka.annotation.*; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.List; import java.util.stream.Collectors; @@ -62,6 +62,6 @@ public void delete(Connector connector) { public List findAllForCluster(String cluster) { return getKafkaStore().values().stream() .filter(connector -> connector.getMetadata().getCluster().equals(cluster)) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaNamespaceRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaNamespaceRepository.java index c40784fe..aa7e194f 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaNamespaceRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaNamespaceRepository.java @@ -4,15 +4,14 @@ import com.michelin.ns4kafka.repositories.NamespaceRepository; import io.micronaut.configuration.kafka.annotation.*; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; - @Singleton @KafkaListener( offsetReset = OffsetReset.EARLIEST, @@ -52,7 +51,7 @@ public List findAllForCluster(String cluster) { return getKafkaStore().values() .stream() .filter(namespace -> namespace.getMetadata().getCluster().equals(cluster)) - .collect(Collectors.toList()); + .toList(); } @Override diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaResourceQuotaRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaResourceQuotaRepository.java index b8fe77e4..d1c02187 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaResourceQuotaRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaResourceQuotaRepository.java @@ -4,10 +4,10 @@ import com.michelin.ns4kafka.repositories.ResourceQuotaRepository; import io.micronaut.configuration.kafka.annotation.*; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaRoleBindingRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaRoleBindingRepository.java index f1a557d2..e375ec17 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaRoleBindingRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaRoleBindingRepository.java @@ -4,10 +4,10 @@ import com.michelin.ns4kafka.repositories.RoleBindingRepository; import io.micronaut.configuration.kafka.annotation.*; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -75,12 +75,13 @@ public void delete(RoleBinding roleBinding) { */ @Override public List findAllForGroups(Collection groups) { - return getKafkaStore().values().stream().filter(roleBinding -> - groups.stream().anyMatch(group -> - roleBinding.getSpec().getSubject().getSubjectType() == RoleBinding.SubjectType.GROUP - && roleBinding.getSpec().getSubject().getSubjectName().equals(group) - ) - ).collect(Collectors.toList()); + return getKafkaStore().values() + .stream() + .filter(roleBinding -> groups + .stream() + .anyMatch(group -> roleBinding.getSpec().getSubject().getSubjectType() == RoleBinding.SubjectType.GROUP + && roleBinding.getSpec().getSubject().getSubjectName().equals(group))) + .toList(); } /** @@ -90,8 +91,9 @@ public List findAllForGroups(Collection groups) { */ @Override public List findAllForNamespace(String namespace) { - return getKafkaStore().values().stream() + return getKafkaStore().values() + .stream() .filter(roleBinding -> roleBinding.getMetadata().getNamespace().equals(namespace)) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStore.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStore.java index 819ac5ea..8aeb48c0 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStore.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStore.java @@ -5,6 +5,8 @@ import io.micronaut.context.annotation.Property; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.TaskScheduler; +import jakarta.inject.Inject; +import jakarta.inject.Named; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.Config; @@ -20,8 +22,6 @@ import org.apache.kafka.common.errors.TopicExistsException; import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.inject.Named; import java.time.Duration; import java.util.Collections; import java.util.Map; @@ -33,9 +33,11 @@ @Slf4j public abstract class KafkaStore { - @Inject ApplicationContext applicationContext; + @Inject + ApplicationContext applicationContext; - @Inject AdminClient adminClient; + @Inject + AdminClient adminClient; @Inject KafkaStoreConfig kafkaStoreConfig; @@ -187,7 +189,7 @@ private void createInternalTopic() throws KafkaStoreException, InterruptedExcept } NewTopic schemaTopicRequest = new NewTopic(kafkaTopic, 1, (short) schemaTopicReplicationFactor); - schemaTopicRequest.configs(kafkaStoreConfig.getProperties()); + schemaTopicRequest.configs(kafkaStoreConfig.getProps()); try { adminClient.createTopics(Collections.singleton(schemaTopicRequest)) diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStreamRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStreamRepository.java index 53ed681a..481d58d0 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStreamRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaStreamRepository.java @@ -1,22 +1,15 @@ package com.michelin.ns4kafka.repositories.kafka; -import java.util.List; -import java.util.stream.Collectors; - -import javax.inject.Singleton; - import com.michelin.ns4kafka.models.KafkaStream; import com.michelin.ns4kafka.repositories.StreamRepository; - +import io.micronaut.configuration.kafka.annotation.*; +import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import io.micronaut.configuration.kafka.annotation.KafkaClient; -import io.micronaut.configuration.kafka.annotation.KafkaListener; -import io.micronaut.configuration.kafka.annotation.OffsetReset; -import io.micronaut.configuration.kafka.annotation.OffsetStrategy; -import io.micronaut.configuration.kafka.annotation.Topic; -import io.micronaut.context.annotation.Value; +import java.util.List; +import java.util.stream.Collectors; @Singleton @KafkaListener( @@ -41,7 +34,7 @@ public List findAllForCluster(String cluster) { return getKafkaStore().values() .stream() .filter(stream -> stream.getMetadata().getCluster().equals(cluster)) - .collect(Collectors.toList()); + .toList(); } @Override diff --git a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaTopicRepository.java b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaTopicRepository.java index 66afd157..d9a1432c 100644 --- a/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaTopicRepository.java +++ b/src/main/java/com/michelin/ns4kafka/repositories/kafka/KafkaTopicRepository.java @@ -7,10 +7,10 @@ import io.micronaut.configuration.kafka.annotation.OffsetReset; import io.micronaut.configuration.kafka.annotation.OffsetStrategy; import io.micronaut.context.annotation.Value; +import jakarta.inject.Singleton; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -76,6 +76,6 @@ public List findAllForCluster(String cluster) { return getKafkaStore().values() .stream() .filter(topic -> topic.getMetadata().getCluster().equals(cluster)) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRule.java b/src/main/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRule.java index ee1ddeb1..71833fab 100644 --- a/src/main/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRule.java +++ b/src/main/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRule.java @@ -5,72 +5,64 @@ import com.michelin.ns4kafka.repositories.NamespaceRepository; import com.michelin.ns4kafka.repositories.RoleBindingRepository; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.async.publisher.Publishers; import io.micronaut.core.util.StringUtils; import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.Authentication; import io.micronaut.security.rules.SecurityRule; import io.micronaut.security.rules.SecurityRuleResult; import io.micronaut.web.router.RouteMatch; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; @Slf4j @Singleton public class ResourceBasedSecurityRule implements SecurityRule { - /** - * Admin role constant - */ public static final String IS_ADMIN = "isAdmin()"; - /** - * The namespaced resource URL pattern - */ private final Pattern namespacedResourcePattern = Pattern.compile("^\\/api\\/namespaces\\/(?[a-zA-Z0-9_-]+)\\/(?[a-z_-]+)(\\/([a-zA-Z0-9_.-]+)(\\/(?[a-z-]+))?)?$"); - /** - * Ns4Kafka security configuration - */ @Inject SecurityConfig securityConfig; - /** - * The role bindings repository - */ @Inject RoleBindingRepository roleBindingRepository; - /** - * The namespace repository - */ @Inject NamespaceRepository namespaceRepository; + @Override + public Publisher check(HttpRequest request, RouteMatch routeMatch, Authentication authentication) { + return Publishers.just(checkSecurity(request, authentication)); + } + /** * Check a user can access a given URL * @param request The current request - * @param routeMatch The matched route or empty if no route was matched. e.g. static resource. - * @param claims The claims from the token. Null if not authenticated + * @param authentication The claims from the token * @return A security rule allowing the user or not */ - @Override - public SecurityRuleResult check(HttpRequest request, @Nullable RouteMatch routeMatch, @Nullable Map claims) { - // Unauthenticated request - if (claims == null || !claims.keySet().containsAll( List.of("groups", "sub", "roles"))) { + public SecurityRuleResult checkSecurity(HttpRequest request, @Nullable Authentication authentication) { + if (authentication == null) { + return SecurityRuleResult.UNKNOWN; + } + + if (!authentication.getAttributes().keySet().containsAll( List.of("groups", "sub", "roles"))) { log.debug("No authentication available for path [{}]. Returning unknown.",request.getPath()); return SecurityRuleResult.UNKNOWN; } - String sub = claims.get("sub").toString(); - List groups = (List) claims.get("groups"); - List roles = (List) claims.get("roles"); + String sub = authentication.getName(); + List groups = (List) authentication.getAttributes().get("groups"); + Collection roles = authentication.getRoles(); // Request to a URL that is not in the scope of this SecurityRule Matcher matcher = namespacedResourcePattern.matcher(request.getPath()); @@ -107,8 +99,12 @@ public SecurityRuleResult check(HttpRequest request, @Nullable RouteMatch List authorizedRoleBindings = roleBindings.stream() .filter(roleBinding -> roleBinding.getMetadata().getNamespace().equals(namespace)) .filter(roleBinding -> roleBinding.getSpec().getRole().getResourceTypes().contains(resourceType)) - .filter(roleBinding -> roleBinding.getSpec().getRole().getVerbs().stream().map(Enum::name).collect(Collectors.toList()).contains(request.getMethodName())) - .collect(Collectors.toList()); + .filter(roleBinding -> roleBinding.getSpec().getRole().getVerbs() + .stream() + .map(Enum::name) + .toList() + .contains(request.getMethodName())) + .toList(); // User not authorized to access requested resource if (authorizedRoleBindings.isEmpty()) { @@ -121,7 +117,6 @@ public SecurityRuleResult check(HttpRequest request, @Nullable RouteMatch log.debug("Authorized user [{}] on path [{}]",sub,request.getPath()); } - return SecurityRuleResult.ALLOWED; } diff --git a/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabApiClient.java b/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabApiClient.java index 8c7bb9c9..7ff66294 100644 --- a/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabApiClient.java +++ b/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabApiClient.java @@ -4,13 +4,11 @@ import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Header; import io.micronaut.http.client.annotation.Client; -import io.reactivex.Flowable; +import io.reactivex.rxjava3.core.Flowable; import java.util.List; import java.util.Map; -import static io.micronaut.http.HttpRequest.GET; - @Client("${micronaut.security.gitlab.url}") public interface GitlabApiClient { /** diff --git a/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationProvider.java b/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationProvider.java index f35dd12f..afce4f9d 100644 --- a/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationProvider.java +++ b/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationProvider.java @@ -1,19 +1,19 @@ package com.michelin.ns4kafka.security.gitlab; -import com.michelin.ns4kafka.security.ResourceBasedSecurityRule; import com.michelin.ns4kafka.config.SecurityConfig; +import com.michelin.ns4kafka.security.ResourceBasedSecurityRule; import com.michelin.ns4kafka.services.RoleBindingService; -import edu.umd.cs.findbugs.annotations.Nullable; +import io.micronaut.core.annotation.Nullable; import io.micronaut.http.HttpRequest; import io.micronaut.security.authentication.*; -import io.reactivex.BackpressureStrategy; -import io.reactivex.Flowable; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.rxjava3.core.BackpressureStrategy; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.schedulers.Schedulers; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.Map; @@ -21,34 +21,20 @@ @Slf4j @Singleton public class GitlabAuthenticationProvider implements AuthenticationProvider { - - /** - * The Gitlab authentication service - */ @Inject GitlabAuthenticationService gitlabAuthenticationService; - /** - * The resource security service - */ @Inject ResourceBasedSecurityRule resourceBasedSecurityRule; - /** - * The role binding service - */ @Inject RoleBindingService roleBindingService; - /** - * The NS4Kafka security config service - */ @Inject SecurityConfig securityConfig; /** * Perform user authentication with GitLab - * * @param httpRequest The HTTP request * @param authenticationRequest The authentication request * @return An authentication response with the user details @@ -68,7 +54,7 @@ public Publisher authenticate(@Nullable HttpRequest h log.debug("Error during authentication: user groups not found in any namespace"); emitter.onError(new AuthenticationException(new AuthenticationFailed("User groups not found in any namespace. There may be an error on the GitLab group of your namespace."))); } else { - UserDetails user = new UserDetails(username, resourceBasedSecurityRule.computeRolesFromGroups(groups), Map.of("groups", groups)); + AuthenticationResponse user = AuthenticationResponse.success(username, resourceBasedSecurityRule.computeRolesFromGroups(groups), Map.of("groups", groups)); emitter.onNext(user); emitter.onComplete(); } diff --git a/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationService.java b/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationService.java index 38ecce16..d89aef58 100644 --- a/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationService.java +++ b/src/main/java/com/michelin/ns4kafka/security/gitlab/GitlabAuthenticationService.java @@ -2,24 +2,18 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.http.HttpResponse; -import io.reactivex.Flowable; -import io.reactivex.Maybe; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Maybe; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; - -import static io.micronaut.http.HttpRequest.GET; @Slf4j @Singleton public class GitlabAuthenticationService { - /** - * The GitLab HTTP client - */ @Inject GitlabApiClient gitlabApiClient; @@ -34,7 +28,7 @@ public Flowable findAllGroups(String token){ response.body() .stream() .map(stringObjectMap -> stringObjectMap.get("full_path").toString()) - .collect(Collectors.toList()) + .toList() ) ); } diff --git a/src/main/java/com/michelin/ns4kafka/security/ldap/LdapAuthenticationMapper.java b/src/main/java/com/michelin/ns4kafka/security/ldap/LdapAuthenticationMapper.java index edfd79ee..45069df0 100644 --- a/src/main/java/com/michelin/ns4kafka/security/ldap/LdapAuthenticationMapper.java +++ b/src/main/java/com/michelin/ns4kafka/security/ldap/LdapAuthenticationMapper.java @@ -1,18 +1,17 @@ package com.michelin.ns4kafka.security.ldap; import com.michelin.ns4kafka.security.ResourceBasedSecurityRule; -import io.micronaut.configuration.security.ldap.ContextAuthenticationMapper; -import io.micronaut.configuration.security.ldap.DefaultContextAuthenticationMapper; -import io.micronaut.configuration.security.ldap.configuration.LdapConfiguration; import io.micronaut.context.annotation.Replaces; import io.micronaut.context.annotation.Requires; import io.micronaut.core.convert.value.ConvertibleValues; import io.micronaut.core.util.StringUtils; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.UserDetails; +import io.micronaut.security.ldap.ContextAuthenticationMapper; +import io.micronaut.security.ldap.DefaultContextAuthenticationMapper; +import io.micronaut.security.ldap.configuration.LdapConfiguration; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.Map; import java.util.Set; @@ -21,11 +20,11 @@ @Replaces(DefaultContextAuthenticationMapper.class) @Requires(property = LdapConfiguration.PREFIX + ".enabled", notEquals = StringUtils.FALSE) public class LdapAuthenticationMapper implements ContextAuthenticationMapper { - @Inject ResourceBasedSecurityRule resourceBasedSecurityRule; + @Override public AuthenticationResponse map(ConvertibleValues attributes, String username, Set groups) { - return new UserDetails(username, resourceBasedSecurityRule.computeRolesFromGroups(List.copyOf(groups)), Map.of("groups",groups)); + return AuthenticationResponse.success(username, resourceBasedSecurityRule.computeRolesFromGroups(List.copyOf(groups)), Map.of("groups",groups)); } } diff --git a/src/main/java/com/michelin/ns4kafka/security/local/LocalUserAuthenticationProvider.java b/src/main/java/com/michelin/ns4kafka/security/local/LocalUserAuthenticationProvider.java index c9b99f0c..97ec520d 100644 --- a/src/main/java/com/michelin/ns4kafka/security/local/LocalUserAuthenticationProvider.java +++ b/src/main/java/com/michelin/ns4kafka/security/local/LocalUserAuthenticationProvider.java @@ -1,17 +1,19 @@ package com.michelin.ns4kafka.security.local; -import com.michelin.ns4kafka.security.ResourceBasedSecurityRule; import com.michelin.ns4kafka.config.SecurityConfig; -import edu.umd.cs.findbugs.annotations.Nullable; +import com.michelin.ns4kafka.security.ResourceBasedSecurityRule; +import io.micronaut.core.annotation.Nullable; import io.micronaut.http.HttpRequest; -import io.micronaut.security.authentication.*; -import io.reactivex.BackpressureStrategy; -import io.reactivex.Flowable; +import io.micronaut.security.authentication.AuthenticationProvider; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.reactivex.rxjava3.core.BackpressureStrategy; +import io.reactivex.rxjava3.core.Flowable; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.Map; import java.util.Optional; @@ -20,12 +22,13 @@ public class LocalUserAuthenticationProvider implements AuthenticationProvider { @Inject SecurityConfig securityConfig; + @Inject ResourceBasedSecurityRule resourceBasedSecurityRule; @Override public Publisher authenticate(@Nullable HttpRequest httpRequest, AuthenticationRequest authenticationRequest) { - Flowable responseFlowable = Flowable.create(emitter -> { + return Flowable.create(emitter -> { String username = authenticationRequest.getIdentity().toString(); String password = authenticationRequest.getSecret().toString(); log.debug("Checking local authentication for user : {}", username); @@ -35,7 +38,7 @@ public Publisher authenticate(@Nullable HttpRequest h .filter(localUser -> localUser.isValidPassword(password)) .findFirst(); if (authenticatedUser.isPresent()) { - UserDetails user = new UserDetails(username, + AuthenticationResponse user = AuthenticationResponse.success(username, resourceBasedSecurityRule.computeRolesFromGroups(authenticatedUser.get().getGroups()), Map.of("groups", authenticatedUser.get().getGroups())); emitter.onNext(user); @@ -43,6 +46,5 @@ public Publisher authenticate(@Nullable HttpRequest h emitter.onComplete(); }, BackpressureStrategy.ERROR); - return responseFlowable; } } diff --git a/src/main/java/com/michelin/ns4kafka/services/AccessControlEntryService.java b/src/main/java/com/michelin/ns4kafka/services/AccessControlEntryService.java index d1277162..ca61940a 100644 --- a/src/main/java/com/michelin/ns4kafka/services/AccessControlEntryService.java +++ b/src/main/java/com/michelin/ns4kafka/services/AccessControlEntryService.java @@ -6,9 +6,9 @@ import com.michelin.ns4kafka.services.executors.AccessControlEntryAsyncExecutor; import io.micronaut.context.ApplicationContext; import io.micronaut.inject.qualifiers.Qualifiers; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -16,27 +16,16 @@ @Singleton public class AccessControlEntryService { - - /** - * The grantedTo value to define public topics. - */ public static final String PUBLIC_GRANTED_TO = "*"; - /** - * The ACL repository - */ @Inject AccessControlEntryRepository accessControlEntryRepository; - /** - * The application context - */ @Inject ApplicationContext applicationContext; /** * Validate a new ACL - * * @param accessControlEntry The ACL * @param namespace The namespace * @return A list of validation errors @@ -150,7 +139,7 @@ public List validateAsAdmin(AccessControlEntry accessControlEntry, Names }) .map(ace -> String.format("AccessControlEntry overlaps with existing one: %s", ace)) - .collect(Collectors.toList()); + .toList(); } /** @@ -228,12 +217,11 @@ public void delete(Namespace namespace, AccessControlEntry accessControlEntry) { * @return A list of ACLs */ public List findAllGrantedToNamespace(Namespace namespace) { - return accessControlEntryRepository.findAll().stream() - .filter(accessControlEntry -> - accessControlEntry.getSpec().getGrantedTo().equals(namespace.getMetadata().getName()) || - accessControlEntry.getSpec().getGrantedTo().equals(PUBLIC_GRANTED_TO) - ) - .collect(Collectors.toList()); + return accessControlEntryRepository.findAll() + .stream() + .filter(accessControlEntry -> accessControlEntry.getSpec().getGrantedTo().equals(namespace.getMetadata().getName()) || + accessControlEntry.getSpec().getGrantedTo().equals(PUBLIC_GRANTED_TO)) + .toList(); } /** @@ -242,9 +230,10 @@ public List findAllGrantedToNamespace(Namespace namespace) { * @return A list of ACLs */ public List findAllPublicGrantedTo() { - return accessControlEntryRepository.findAll().stream() + return accessControlEntryRepository.findAll() + .stream() .filter(accessControlEntry -> accessControlEntry.getSpec().getGrantedTo().equals(PUBLIC_GRANTED_TO)) - .collect(Collectors.toList()); + .toList(); } /** @@ -256,7 +245,7 @@ public List findAllPublicGrantedTo() { public List findAllForNamespace(Namespace namespace) { return accessControlEntryRepository.findAll().stream() .filter(accessControlEntry -> accessControlEntry.getMetadata().getNamespace().equals(namespace.getMetadata().getName())) - .collect(Collectors.toList()); + .toList(); } /** @@ -268,7 +257,7 @@ public List findAllForNamespace(Namespace namespace) { public List findAllForCluster(String cluster) { return accessControlEntryRepository.findAll().stream() .filter(accessControlEntry -> accessControlEntry.getMetadata().getCluster().equals(cluster)) - .collect(Collectors.toList()); + .toList(); } /** diff --git a/src/main/java/com/michelin/ns4kafka/services/ConnectClusterService.java b/src/main/java/com/michelin/ns4kafka/services/ConnectClusterService.java index 3d304925..246ae492 100644 --- a/src/main/java/com/michelin/ns4kafka/services/ConnectClusterService.java +++ b/src/main/java/com/michelin/ns4kafka/services/ConnectClusterService.java @@ -8,18 +8,18 @@ import com.michelin.ns4kafka.repositories.ConnectClusterRepository; import com.michelin.ns4kafka.utils.EncryptionUtils; import com.nimbusds.jose.JOSEException; +import io.micronaut.core.util.StringUtils; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.http.MutableHttpRequest; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientException; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; -import javax.inject.Singleton; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -45,7 +45,7 @@ public class ConnectClusterService { @Inject @Client("/") - RxHttpClient httpClient; + Rx3HttpClient httpClient; /** * Find all self deployed Connect clusters @@ -64,22 +64,18 @@ public List findAll() { public List findAllByNamespace(Namespace namespace, List permissions) { List acls = accessControlEntryService.findAllGrantedToNamespace(namespace).stream() .filter(acl -> permissions.contains(acl.getSpec().getPermission())) - .filter(acl -> acl.getSpec().getResourceType() == AccessControlEntry.ResourceType.CONNECT_CLUSTER) - .collect(Collectors.toList()); + .filter(acl -> acl.getSpec().getResourceType() == AccessControlEntry.ResourceType.CONNECT_CLUSTER).toList(); return connectClusterRepository.findAllForCluster(namespace.getMetadata().getCluster()) .stream() - .filter(connector -> acls.stream().anyMatch(accessControlEntry -> { - switch (accessControlEntry.getSpec().getResourcePatternType()) { - case PREFIXED: - return connector.getMetadata().getName().startsWith(accessControlEntry.getSpec().getResource()); - case LITERAL: - return connector.getMetadata().getName().equals(accessControlEntry.getSpec().getResource()); - } - - return false; + .filter(connector -> acls.stream().anyMatch(accessControlEntry -> + switch (accessControlEntry.getSpec().getResourcePatternType()) { + case PREFIXED -> + connector.getMetadata().getName().startsWith(accessControlEntry.getSpec().getResource()); + case LITERAL -> + connector.getMetadata().getName().equals(accessControlEntry.getSpec().getResource()); })) - .collect(Collectors.toList()); + .toList(); } /** @@ -98,7 +94,7 @@ public List findAllByNamespaceOwner(Namespace namespace) { .password(EncryptionUtils.decryptAES256GCM(connectCluster.getSpec().getPassword(), securityConfig.getAes256EncryptionKey())) .build()) .build()) - .collect(Collectors.toList()); + .toList(); } /** @@ -129,7 +125,7 @@ public Optional findByNamespaceAndNameOwner(Namespace namespace, * @return The created connect worker */ public ConnectCluster create(ConnectCluster connectCluster) throws IOException, JOSEException { - if (StringUtils.isNotBlank(connectCluster.getSpec().getPassword())) { + if (StringUtils.hasText(connectCluster.getSpec().getPassword())) { connectCluster.getSpec() .setPassword(EncryptionUtils.encryptAES256GCM(connectCluster.getSpec().getPassword(), securityConfig.getAes256EncryptionKey())); } @@ -151,7 +147,7 @@ public List validateConnectClusterCreation(ConnectCluster connectCluster try { MutableHttpRequest request = HttpRequest.GET(new URL(connectCluster.getSpec().getUrl()) + "/connectors?expand=info&expand=status"); - if (StringUtils.isNotBlank(connectCluster.getSpec().getUsername()) && StringUtils.isNotBlank(connectCluster.getSpec().getPassword())){ + if (StringUtils.hasText(connectCluster.getSpec().getUsername()) && StringUtils.hasText(connectCluster.getSpec().getPassword())){ request.basicAuth(connectCluster.getSpec().getUsername(), connectCluster.getSpec().getPassword()); } HttpResponse response = httpClient.exchange(request).blockingFirst(); diff --git a/src/main/java/com/michelin/ns4kafka/services/ConnectorService.java b/src/main/java/com/michelin/ns4kafka/services/ConnectorService.java index 04595cec..6cd9595f 100644 --- a/src/main/java/com/michelin/ns4kafka/services/ConnectorService.java +++ b/src/main/java/com/michelin/ns4kafka/services/ConnectorService.java @@ -1,8 +1,8 @@ package com.michelin.ns4kafka.services; import com.michelin.ns4kafka.models.AccessControlEntry; -import com.michelin.ns4kafka.models.connector.Connector; import com.michelin.ns4kafka.models.Namespace; +import com.michelin.ns4kafka.models.connector.Connector; import com.michelin.ns4kafka.repositories.ConnectorRepository; import com.michelin.ns4kafka.services.connect.ConnectorClientProxy; import com.michelin.ns4kafka.services.connect.client.ConnectorClient; @@ -12,12 +12,12 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.http.HttpResponse; import io.micronaut.inject.qualifiers.Qualifiers; -import io.reactivex.Observable; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Single; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -65,7 +65,7 @@ public List findAllForNamespace(Namespace namespace) { } return false; })) - .collect(Collectors.toList()); + .toList(); } /** @@ -78,7 +78,7 @@ public List findAllByConnectCluster(Namespace namespace, String conne return connectorRepository.findAllForCluster(namespace.getMetadata().getCluster()) .stream() .filter(connector -> connector.getSpec().getConnectCluster().equals(connectCluster)) - .collect(Collectors.toList()); + .toList(); } /** @@ -104,8 +104,7 @@ public Single> validateLocally(Namespace namespace, Connector conne // Check whether target Connect Cluster is allowed for this namespace List selfDeployedConnectClusters = connectClusterService.findAllByNamespaceWrite(namespace) .stream() - .map(connectCluster -> connectCluster.getMetadata().getName()) - .collect(Collectors.toList()); + .map(connectCluster -> connectCluster.getMetadata().getName()).toList(); if (!namespace.getSpec().getConnectClusters().contains(connector.getSpec().getConnectCluster()) && !selfDeployedConnectClusters.contains(connector.getSpec().getConnectCluster())) { @@ -185,7 +184,7 @@ public Single> delete(Namespace namespace, Connector connecto return connectorClient.delete(ConnectorClientProxy.PROXY_SECRET, namespace.getMetadata().getCluster(), connector.getSpec().getConnectCluster(), connector.getMetadata().getName()) .defaultIfEmpty(HttpResponse.noContent()) - .flatMapSingle(httpResponse -> { + .map(httpResponse -> { connectorRepository.delete(connector); if (log.isInfoEnabled()) { @@ -194,7 +193,7 @@ public Single> delete(Namespace namespace, Connector connecto "] Connect [" + connector.getSpec().getConnectCluster() + "]"); } - return Single.just(httpResponse); + return httpResponse; }); } @@ -216,7 +215,7 @@ public Single> listUnsynchronizedConnectors(Namespace namespace) .filter(connector -> isNamespaceOwnerOfConnect(namespace, connector.getMetadata().getName())) // And aren't in ns4kafka storage .filter(connector -> findByName(namespace, connector.getMetadata().getName()).isEmpty())) - .collect(Collectors.toList()); + .toList(); return Observable.merge(connectors).toList(); } diff --git a/src/main/java/com/michelin/ns4kafka/services/ConsumerGroupService.java b/src/main/java/com/michelin/ns4kafka/services/ConsumerGroupService.java index 22b1e424..79aa02a2 100644 --- a/src/main/java/com/michelin/ns4kafka/services/ConsumerGroupService.java +++ b/src/main/java/com/michelin/ns4kafka/services/ConsumerGroupService.java @@ -1,16 +1,16 @@ package com.michelin.ns4kafka.services; import com.michelin.ns4kafka.models.AccessControlEntry; +import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.models.consumer.group.ConsumerGroupResetOffsets; import com.michelin.ns4kafka.models.consumer.group.ConsumerGroupResetOffsets.ResetOffsetsMethod; -import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.services.executors.ConsumerGroupAsyncExecutor; import io.micronaut.context.ApplicationContext; import io.micronaut.inject.qualifiers.Qualifiers; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.apache.kafka.common.TopicPartition; -import javax.inject.Inject; -import javax.inject.Singleton; import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; @@ -24,15 +24,9 @@ @Singleton public class ConsumerGroupService { - /** - * The application context - */ @Inject ApplicationContext applicationContext; - /** - * The ACL service - */ @Inject AccessControlEntryService accessControlEntryService; diff --git a/src/main/java/com/michelin/ns4kafka/services/NamespaceService.java b/src/main/java/com/michelin/ns4kafka/services/NamespaceService.java index e2b21737..a9ccc385 100644 --- a/src/main/java/com/michelin/ns4kafka/services/NamespaceService.java +++ b/src/main/java/com/michelin/ns4kafka/services/NamespaceService.java @@ -1,11 +1,11 @@ package com.michelin.ns4kafka.services; +import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.repositories.NamespaceRepository; -import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -62,7 +62,7 @@ public List validate(Namespace namespace) { .stream() .filter(connectCluster -> !connectClusterExists(namespace.getMetadata().getCluster(), connectCluster)) .map(s -> "Invalid value " + s + " for Connect Cluster: Connect Cluster doesn't exist") - .collect(Collectors.toList()); + .toList(); } /** @@ -93,7 +93,7 @@ public List listAll() { return kafkaAsyncExecutorConfigList.stream() .map(KafkaAsyncExecutorConfig::getName) .flatMap(s -> namespaceRepository.findAllForCluster(s).stream()) - .collect(Collectors.toList()); + .toList(); } public List listAllNamespaceResources(Namespace namespace){ @@ -112,6 +112,6 @@ public List listAllNamespaceResources(Namespace namespace){ ) .reduce(Stream::concat) .orElseGet(Stream::empty) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/com/michelin/ns4kafka/services/ResourceQuotaService.java b/src/main/java/com/michelin/ns4kafka/services/ResourceQuotaService.java index 56bf74e6..7f6b6ec8 100644 --- a/src/main/java/com/michelin/ns4kafka/services/ResourceQuotaService.java +++ b/src/main/java/com/michelin/ns4kafka/services/ResourceQuotaService.java @@ -7,12 +7,14 @@ import com.michelin.ns4kafka.models.quota.ResourceQuotaResponse; import com.michelin.ns4kafka.repositories.ResourceQuotaRepository; import com.michelin.ns4kafka.utils.BytesUtils; +import io.micronaut.core.util.StringUtils; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static com.michelin.ns4kafka.models.quota.ResourceQuota.ResourceQuotaSpecKey.*; @@ -23,7 +25,9 @@ @Singleton public class ResourceQuotaService { private static final String QUOTA_ALREADY_EXCEEDED_ERROR = "Quota already exceeded for %s: %s/%s (used/limit)"; + private static final String QUOTA_RESPONSE_FORMAT = "%s/%s"; + private static final String NO_QUOTA_RESPONSE_FORMAT = "%s"; @Inject @@ -84,7 +88,7 @@ public void delete(ResourceQuota resourceQuota) { public List validateNewResourceQuota(Namespace namespace, ResourceQuota resourceQuota) { List errors = new ArrayList<>(); - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(COUNT_TOPICS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(COUNT_TOPICS.getKey()))) { long used = getCurrentCountTopicsByNamespace(namespace); long limit = Long.parseLong(resourceQuota.getSpec().get(COUNT_TOPICS.getKey())); if (used > limit) { @@ -92,7 +96,7 @@ public List validateNewResourceQuota(Namespace namespace, ResourceQuota } } - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(COUNT_PARTITIONS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(COUNT_PARTITIONS.getKey()))) { long used = getCurrentCountPartitionsByNamespace(namespace); long limit = Long.parseLong(resourceQuota.getSpec().get(COUNT_PARTITIONS.getKey())); if (used > limit) { @@ -100,7 +104,7 @@ public List validateNewResourceQuota(Namespace namespace, ResourceQuota } } - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(DISK_TOPICS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(DISK_TOPICS.getKey()))) { String limitAsString = resourceQuota.getSpec().get(DISK_TOPICS.getKey()); if (!limitAsString.endsWith(BYTE) && !limitAsString.endsWith(KIBIBYTE) && !limitAsString.endsWith(MEBIBYTE) && !limitAsString.endsWith(GIBIBYTE)) { errors.add(String.format("Invalid value for %s: value must end with either %s, %s, %s or %s", @@ -115,7 +119,7 @@ public List validateNewResourceQuota(Namespace namespace, ResourceQuota } } - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(COUNT_CONNECTORS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(COUNT_CONNECTORS.getKey()))) { long used = getCurrentCountConnectorsByNamespace(namespace); long limit = Long.parseLong(resourceQuota.getSpec().get(COUNT_CONNECTORS.getKey())); if (used > limit) { @@ -188,7 +192,7 @@ public List validateTopicQuota(Namespace namespace, Optional exis // Check count topics and count partitions only at creation if (existingTopic.isEmpty()) { - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(COUNT_TOPICS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(COUNT_TOPICS.getKey()))) { long used = getCurrentCountTopicsByNamespace(namespace); long limit = Long.parseLong(resourceQuota.getSpec().get(COUNT_TOPICS.getKey())); if (used + 1 > limit) { @@ -196,7 +200,7 @@ public List validateTopicQuota(Namespace namespace, Optional exis } } - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(COUNT_PARTITIONS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(COUNT_PARTITIONS.getKey()))) { long used = getCurrentCountPartitionsByNamespace(namespace); long limit = Long.parseLong(resourceQuota.getSpec().get(COUNT_PARTITIONS.getKey())); if (used + newTopic.getSpec().getPartitions() > limit) { @@ -205,8 +209,8 @@ public List validateTopicQuota(Namespace namespace, Optional exis } } - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(DISK_TOPICS.getKey())) && - StringUtils.isNotBlank(newTopic.getSpec().getConfigs().get(RETENTION_BYTES_CONFIG))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(DISK_TOPICS.getKey())) && + StringUtils.hasText(newTopic.getSpec().getConfigs().get(RETENTION_BYTES_CONFIG))) { long used = getCurrentDiskTopicsByNamespace(namespace); long limit = BytesUtils.humanReadableToBytes(resourceQuota.getSpec().get(DISK_TOPICS.getKey())); @@ -241,7 +245,7 @@ public List validateConnectorQuota(Namespace namespace) { List errors = new ArrayList<>(); ResourceQuota resourceQuota = resourceQuotaOptional.get(); - if (StringUtils.isNotBlank(resourceQuota.getSpec().get(COUNT_CONNECTORS.getKey()))) { + if (StringUtils.hasText(resourceQuota.getSpec().get(COUNT_CONNECTORS.getKey()))) { long used = getCurrentCountConnectorsByNamespace(namespace); long limit = Long.parseLong(resourceQuota.getSpec().get(COUNT_CONNECTORS.getKey())); if (used + 1 > limit) { @@ -260,7 +264,7 @@ public List getUsedResourcesByQuotaForAllNamespaces() { return namespaceService.listAll() .stream() .map(namespace -> getUsedResourcesByQuotaByNamespace(namespace, findByNamespace(namespace.getMetadata().getName()))) - .collect(Collectors.toList()); + .toList(); } /** @@ -291,19 +295,19 @@ public ResourceQuotaResponse getUsedResourcesByQuotaByNamespace(Namespace namesp */ public ResourceQuotaResponse formatUsedResourceByQuotaResponse(Namespace namespace, long currentCountTopic, long currentCountPartition, long currentDiskTopic, long currentCountConnector, Optional resourceQuota) { - String countTopic = resourceQuota.isPresent() && StringUtils.isNotBlank(resourceQuota.get().getSpec().get(COUNT_TOPICS.getKey())) ? + String countTopic = resourceQuota.isPresent() && StringUtils.hasText(resourceQuota.get().getSpec().get(COUNT_TOPICS.getKey())) ? String.format(QUOTA_RESPONSE_FORMAT, currentCountTopic, resourceQuota.get().getSpec().get(COUNT_TOPICS.getKey())) : String.format(NO_QUOTA_RESPONSE_FORMAT, currentCountTopic); - String countPartition = resourceQuota.isPresent() && StringUtils.isNotBlank(resourceQuota.get().getSpec().get(COUNT_PARTITIONS.getKey())) ? + String countPartition = resourceQuota.isPresent() && StringUtils.hasText(resourceQuota.get().getSpec().get(COUNT_PARTITIONS.getKey())) ? String.format(QUOTA_RESPONSE_FORMAT, currentCountPartition, resourceQuota.get().getSpec().get(COUNT_PARTITIONS.getKey())) : String.format(NO_QUOTA_RESPONSE_FORMAT, currentCountPartition); - String diskTopic = resourceQuota.isPresent() && StringUtils.isNotBlank(resourceQuota.get().getSpec().get(DISK_TOPICS.getKey())) ? + String diskTopic = resourceQuota.isPresent() && StringUtils.hasText(resourceQuota.get().getSpec().get(DISK_TOPICS.getKey())) ? String.format(QUOTA_RESPONSE_FORMAT, BytesUtils.bytesToHumanReadable(currentDiskTopic), resourceQuota.get().getSpec().get(DISK_TOPICS.getKey())) : String.format(NO_QUOTA_RESPONSE_FORMAT, BytesUtils.bytesToHumanReadable(currentDiskTopic)); - String countConnector = resourceQuota.isPresent() && StringUtils.isNotBlank(resourceQuota.get().getSpec().get(COUNT_CONNECTORS.getKey())) ? + String countConnector = resourceQuota.isPresent() && StringUtils.hasText(resourceQuota.get().getSpec().get(COUNT_CONNECTORS.getKey())) ? String.format(QUOTA_RESPONSE_FORMAT, currentCountConnector, resourceQuota.get().getSpec().get(COUNT_CONNECTORS.getKey())) : String.format(NO_QUOTA_RESPONSE_FORMAT, currentCountConnector); diff --git a/src/main/java/com/michelin/ns4kafka/services/RoleBindingService.java b/src/main/java/com/michelin/ns4kafka/services/RoleBindingService.java index 916a96f0..9fe3c1c5 100644 --- a/src/main/java/com/michelin/ns4kafka/services/RoleBindingService.java +++ b/src/main/java/com/michelin/ns4kafka/services/RoleBindingService.java @@ -2,18 +2,15 @@ import com.michelin.ns4kafka.models.RoleBinding; import com.michelin.ns4kafka.repositories.RoleBindingRepository; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.Collection; import java.util.List; import java.util.Optional; @Singleton public class RoleBindingService { - /** - * The role binding repository - */ @Inject RoleBindingRepository roleBindingRepository; diff --git a/src/main/java/com/michelin/ns4kafka/services/SchemaService.java b/src/main/java/com/michelin/ns4kafka/services/SchemaService.java index d24a31a1..510ec38e 100644 --- a/src/main/java/com/michelin/ns4kafka/services/SchemaService.java +++ b/src/main/java/com/michelin/ns4kafka/services/SchemaService.java @@ -10,12 +10,12 @@ import com.michelin.ns4kafka.services.schema.client.entities.SchemaCompatibilityResponse; import com.michelin.ns4kafka.services.schema.client.entities.SchemaRequest; import com.michelin.ns4kafka.services.schema.client.entities.SchemaResponse; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -23,15 +23,9 @@ @Slf4j @Singleton public class SchemaService { - /** - * ACLs service - */ @Inject AccessControlEntryService accessControlEntryService; - /** - * Schema Registry client - */ @Inject KafkaSchemaRegistryClient kafkaSchemaRegistryClient; @@ -43,8 +37,7 @@ public class SchemaService { public Single> findAllForNamespace(Namespace namespace) { List acls = accessControlEntryService.findAllGrantedToNamespace(namespace).stream() .filter(acl -> acl.getSpec().getPermission() == AccessControlEntry.Permission.OWNER) - .filter(acl -> acl.getSpec().getResourceType() == AccessControlEntry.ResourceType.TOPIC) - .collect(Collectors.toList()); + .filter(acl -> acl.getSpec().getResourceType() == AccessControlEntry.ResourceType.TOPIC).toList(); return kafkaSchemaRegistryClient .getSubjects(KafkaSchemaRegistryClientProxy.PROXY_SECRET, namespace.getMetadata().getCluster()) @@ -53,15 +46,11 @@ public Single> findAllForNamespace(Namespace namespace) { .filter(subject -> { String underlyingTopicName = subject.replaceAll("(-key|-value)$",""); - return acls.stream().anyMatch(accessControlEntry -> { - switch (accessControlEntry.getSpec().getResourcePatternType()) { - case PREFIXED: - return underlyingTopicName.startsWith(accessControlEntry.getSpec().getResource()); - case LITERAL: - return underlyingTopicName.equals(accessControlEntry.getSpec().getResource()); - } - - return false; + return acls.stream().anyMatch(accessControlEntry -> switch (accessControlEntry.getSpec().getResourcePatternType()) { + case PREFIXED -> + underlyingTopicName.startsWith(accessControlEntry.getSpec().getResource()); + case LITERAL -> + underlyingTopicName.equals(accessControlEntry.getSpec().getResource()); }); }) .map(namespacedSubject -> SchemaList.builder() @@ -85,37 +74,30 @@ public Single> findAllForNamespace(Namespace namespace) { public Maybe getLatestSubject(Namespace namespace, String subject) { return kafkaSchemaRegistryClient .getLatestSubject(KafkaSchemaRegistryClientProxy.PROXY_SECRET, namespace.getMetadata().getCluster(), subject) - .map(Optional::of) - .defaultIfEmpty(Optional.empty()) - .flatMap(latestSubjectOptional -> { - if (latestSubjectOptional.isEmpty()) { - return Maybe.empty(); - } - - return kafkaSchemaRegistryClient - .getCurrentCompatibilityBySubject(KafkaSchemaRegistryClientProxy.PROXY_SECRET, namespace.getMetadata().getCluster(), subject) - .map(Optional::of) - .defaultIfEmpty(Optional.empty()) - .map(currentCompatibilityOptional -> { - Schema.Compatibility compatibility = currentCompatibilityOptional.isPresent() ? currentCompatibilityOptional.get().compatibilityLevel() : Schema.Compatibility.GLOBAL; - - return Schema.builder() - .metadata(ObjectMeta.builder() - .cluster(namespace.getMetadata().getCluster()) - .namespace(namespace.getMetadata().getName()) - .name(latestSubjectOptional.get().subject()) - .build()) - .spec(Schema.SchemaSpec.builder() - .id(latestSubjectOptional.get().id()) - .version(latestSubjectOptional.get().version()) - .compatibility(compatibility) - .schema(latestSubjectOptional.get().schema()) - .schemaType(latestSubjectOptional.get().schemaType() == null ? Schema.SchemaType.AVRO : - Schema.SchemaType.valueOf(latestSubjectOptional.get().schemaType())) - .build()) - .build(); - }); - }); + .flatMap(latestSubjectOptional -> kafkaSchemaRegistryClient + .getCurrentCompatibilityBySubject(KafkaSchemaRegistryClientProxy.PROXY_SECRET, namespace.getMetadata().getCluster(), subject) + .map(Optional::of) + .defaultIfEmpty(Optional.empty()) + .map(currentCompatibilityOptional -> { + Schema.Compatibility compatibility = currentCompatibilityOptional.isPresent() ? currentCompatibilityOptional.get().compatibilityLevel() : Schema.Compatibility.GLOBAL; + + return Schema.builder() + .metadata(ObjectMeta.builder() + .cluster(namespace.getMetadata().getCluster()) + .namespace(namespace.getMetadata().getName()) + .name(latestSubjectOptional.subject()) + .build()) + .spec(Schema.SchemaSpec.builder() + .id(latestSubjectOptional.id()) + .version(latestSubjectOptional.version()) + .compatibility(compatibility) + .schema(latestSubjectOptional.schema()) + .schemaType(latestSubjectOptional.schemaType() == null ? Schema.SchemaType.AVRO : + Schema.SchemaType.valueOf(latestSubjectOptional.schemaType())) + .build()) + .build(); + }) + .toMaybe()); } /** @@ -166,17 +148,17 @@ public Single> validateSchemaCompatibility(String cluster, Schema s .build()) .map(Optional::of) .defaultIfEmpty(Optional.empty()) - .flatMapSingle(schemaCompatibilityCheckOptional -> { - if (schemaCompatibilityCheckOptional.isEmpty()) { - return Single.just(List.of()); - } + .map(schemaCompatibilityCheckOptional -> { + if (schemaCompatibilityCheckOptional.isEmpty()) { + return List.of(); + } - if (!schemaCompatibilityCheckOptional.get().isCompatible()) { - return Single.just(schemaCompatibilityCheckOptional.get().messages()); - } + if (!schemaCompatibilityCheckOptional.get().isCompatible()) { + return schemaCompatibilityCheckOptional.get().messages(); + } - return Single.just(List.of()); - }); + return List.of(); + }); } /** diff --git a/src/main/java/com/michelin/ns4kafka/services/StreamService.java b/src/main/java/com/michelin/ns4kafka/services/StreamService.java index f695c582..fdaecb6f 100644 --- a/src/main/java/com/michelin/ns4kafka/services/StreamService.java +++ b/src/main/java/com/michelin/ns4kafka/services/StreamService.java @@ -1,33 +1,27 @@ package com.michelin.ns4kafka.services; -import com.michelin.ns4kafka.models.*; +import com.michelin.ns4kafka.models.AccessControlEntry; +import com.michelin.ns4kafka.models.KafkaStream; +import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.repositories.StreamRepository; import com.michelin.ns4kafka.services.executors.AccessControlEntryAsyncExecutor; import io.micronaut.context.ApplicationContext; import io.micronaut.inject.qualifiers.Qualifiers; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.*; +import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Singleton public class StreamService { - /** - * The Kafka Streams repository - */ @Inject StreamRepository streamRepository; - /** - * The ACL service - */ @Inject AccessControlEntryService accessControlEntryService; - /** - * The application context - */ @Inject ApplicationContext applicationContext; @@ -39,7 +33,7 @@ public class StreamService { public List findAllForNamespace(Namespace namespace) { return streamRepository.findAllForCluster(namespace.getMetadata().getCluster()).stream() .filter(stream -> stream.getMetadata().getNamespace().equals(namespace.getMetadata().getName())) - .collect(Collectors.toList()); + .toList(); } /** diff --git a/src/main/java/com/michelin/ns4kafka/services/TopicService.java b/src/main/java/com/michelin/ns4kafka/services/TopicService.java index 89562c1f..0a438288 100644 --- a/src/main/java/com/michelin/ns4kafka/services/TopicService.java +++ b/src/main/java/com/michelin/ns4kafka/services/TopicService.java @@ -1,18 +1,18 @@ package com.michelin.ns4kafka.services; +import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; import com.michelin.ns4kafka.models.AccessControlEntry; import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.models.Topic; import com.michelin.ns4kafka.repositories.TopicRepository; -import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; import com.michelin.ns4kafka.services.executors.TopicAsyncExecutor; import io.micronaut.context.ApplicationContext; import io.micronaut.inject.qualifiers.Qualifiers; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.apache.kafka.clients.admin.RecordsToDelete; import org.apache.kafka.common.TopicPartition; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -22,27 +22,15 @@ @Singleton public class TopicService { - /** - * The topic repository - */ @Inject TopicRepository topicRepository; - /** - * The ACL service - */ @Inject AccessControlEntryService accessControlEntryService; - /** - * The application context - */ @Inject ApplicationContext applicationContext; - /** - * The managed cluster config - */ @Inject List kafkaAsyncExecutorConfig; @@ -78,7 +66,7 @@ public List findAllForNamespace(Namespace namespace) { } return false; })) - .collect(Collectors.toList()); + .toList(); } /** @@ -145,7 +133,7 @@ public List findCollidingTopics(Namespace namespace, Topic topic) throws // this topic could be created on ns4kafka during "import" step .filter(clusterTopic -> !topic.getMetadata().getName().equals(clusterTopic)) .filter(clusterTopic -> hasCollision(clusterTopic, topic.getMetadata().getName())) - .collect(Collectors.toList()); + .toList(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedException(e.getMessage()); @@ -236,7 +224,7 @@ public List listUnsynchronizedTopicNames(Namespace namespace) throws Exe .filter(topic -> isNamespaceOwnerOfTopic(namespace.getMetadata().getName(), topic)) // ...and aren't in ns4kafka storage .filter(topic -> findByName(namespace, topic).isEmpty()) - .collect(Collectors.toList()); + .toList(); } /** diff --git a/src/main/java/com/michelin/ns4kafka/services/connect/ConnectorClientProxy.java b/src/main/java/com/michelin/ns4kafka/services/connect/ConnectorClientProxy.java index b95d7b69..b456f553 100644 --- a/src/main/java/com/michelin/ns4kafka/services/connect/ConnectorClientProxy.java +++ b/src/main/java/com/michelin/ns4kafka/services/connect/ConnectorClientProxy.java @@ -15,12 +15,12 @@ import io.micronaut.http.MutableHttpResponse; import io.micronaut.http.annotation.Filter; import io.micronaut.http.client.ProxyHttpClient; -import io.micronaut.http.filter.OncePerRequestHttpServerFilter; +import io.micronaut.http.filter.HttpServerFilter; import io.micronaut.http.filter.ServerFilterChain; +import jakarta.inject.Inject; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; -import javax.inject.Inject; import java.net.URI; import java.util.List; import java.util.Optional; @@ -28,31 +28,15 @@ @Slf4j @Filter(ConnectorClientProxy.PROXY_PREFIX + "/**") -public class ConnectorClientProxy extends OncePerRequestHttpServerFilter { - /** - * Prefix used to filter request to Connect clusters. It'll be replaced by - * the Connect cluster URL of the given cluster - */ +public class ConnectorClientProxy implements HttpServerFilter { public static final String PROXY_PREFIX = "/connect-proxy"; - /** - * A header that contains the Kafka cluster - */ public static final String PROXY_HEADER_KAFKA_CLUSTER = "X-Kafka-Cluster"; - /** - * A header that contains the Connect cluster name - */ public static final String PROXY_HEADER_CONNECT_CLUSTER = "X-Connect-Cluster"; - /** - * A header that contains a secret for the request - */ public static final String PROXY_HEADER_SECRET = "X-Proxy-Secret"; - /** - * Generate a secret - */ public static final String PROXY_SECRET = UUID.randomUUID().toString(); @Inject @@ -74,7 +58,7 @@ public class ConnectorClientProxy extends OncePerRequestHttpServerFilter { * @return A modified request */ @Override - public Publisher> doFilterOnce(HttpRequest request, ServerFilterChain chain) { + public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { // Check call is initiated from Micronaut and not from outside if (!request.getHeaders().contains(ConnectorClientProxy.PROXY_HEADER_SECRET)) { return Publishers.just(new ResourceValidationException(List.of("Missing required header " + ConnectorClientProxy.PROXY_HEADER_SECRET), null, null)); diff --git a/src/main/java/com/michelin/ns4kafka/services/connect/client/ConnectorClient.java b/src/main/java/com/michelin/ns4kafka/services/connect/client/ConnectorClient.java index cc319f3d..22ad856c 100644 --- a/src/main/java/com/michelin/ns4kafka/services/connect/client/ConnectorClient.java +++ b/src/main/java/com/michelin/ns4kafka/services/connect/client/ConnectorClient.java @@ -5,8 +5,8 @@ import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.*; import io.micronaut.http.client.annotation.Client; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/michelin/ns4kafka/services/executors/AccessControlEntryAsyncExecutor.java b/src/main/java/com/michelin/ns4kafka/services/executors/AccessControlEntryAsyncExecutor.java index 9baff36f..a5720074 100644 --- a/src/main/java/com/michelin/ns4kafka/services/executors/AccessControlEntryAsyncExecutor.java +++ b/src/main/java/com/michelin/ns4kafka/services/executors/AccessControlEntryAsyncExecutor.java @@ -10,6 +10,8 @@ import com.michelin.ns4kafka.services.ConnectorService; import com.michelin.ns4kafka.services.StreamService; import io.micronaut.context.annotation.EachBean; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.Admin; import org.apache.kafka.common.acl.AclBinding; @@ -20,8 +22,6 @@ import org.apache.kafka.common.resource.ResourcePattern; import org.apache.kafka.common.resource.ResourceType; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -37,40 +37,20 @@ @EachBean(KafkaAsyncExecutorConfig.class) @Singleton public class AccessControlEntryAsyncExecutor { - /** - * The managed clusters configuration - */ private final KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig; - /** - * The ACL service - */ @Inject AccessControlEntryService accessControlEntryService; - /** - * The Kafka Streams Service - */ @Inject StreamService streamService; - /** - * The Kafka Connect service - */ @Inject ConnectorService connectorService; - /** - * The namespace repository - */ @Inject NamespaceRepository namespaceRepository; - /** - * Constructor - * - * @param kafkaAsyncExecutorConfig The managed clusters configuration - */ public AccessControlEntryAsyncExecutor(KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig) { this.kafkaAsyncExecutorConfig = kafkaAsyncExecutorConfig; } @@ -99,11 +79,11 @@ private void synchronizeACLs() { List toCreate = ns4kafkaACLs.stream() .filter(aclBinding -> !brokerACLs.contains(aclBinding)) - .collect(Collectors.toList()); + .toList(); List toDelete = brokerACLs.stream() .filter(aclBinding -> !ns4kafkaACLs.contains(aclBinding)) - .collect(Collectors.toList()); + .toList(); if (log.isDebugEnabled()) { brokerACLs.stream() @@ -173,7 +153,7 @@ private List collectNs4KafkaACLs() { List ns4kafkaACLs = Stream.of(aclBindingFromACLs, aclBindingFromKStream, aclBindingFromConnect) .flatMap(Function.identity()) - .collect(Collectors.toList()); + .toList(); if (log.isDebugEnabled()) { log.debug("ACLs found on ns4kafka : " + ns4kafkaACLs.size()); @@ -204,7 +184,7 @@ private List collectBrokerACLs(boolean managedUsersOnly) throws Exec .values().get(10, TimeUnit.SECONDS) .stream() .filter(aclBinding -> validResourceTypes.contains(aclBinding.pattern().resourceType())) - .collect(Collectors.toList()); + .toList(); log.debug("{} ACLs found on broker", userACLs.size()); if (log.isTraceEnabled()) { @@ -220,16 +200,15 @@ private List collectBrokerACLs(boolean managedUsersOnly) throws Exec //TODO managed user list should include not only "defaultKafkaUser" (MVP35) //1-N Namespace to KafkaUser .flatMap(namespace -> List.of("User:" + namespace.getSpec().getKafkaUser()).stream()) - .collect(Collectors.toList()); + .toList(); // And then filter out the AclBinding to retain only those matching // or having principal equal to wildcard (public). - userACLs = userACLs.stream() - .filter(aclBinding -> - managedUsers.contains(aclBinding.entry().principal()) || - aclBinding.entry().principal().equals(PUBLIC_GRANTED_TO) - ) - .collect(Collectors.toList()); + userACLs = userACLs + .stream() + .filter(aclBinding -> managedUsers.contains(aclBinding.entry().principal()) || + aclBinding.entry().principal().equals(PUBLIC_GRANTED_TO)) + .toList(); log.debug("ACLs found on Broker (managed scope) : {}", userACLs.size()); } @@ -267,7 +246,7 @@ private List buildAclBindingsFromAccessControlEntry(AccessControlEnt .map(aclOperation -> new AclBinding(resourcePattern, new org.apache.kafka.common.acl.AccessControlEntry("User:" + aclUser, "*", aclOperation, AclPermissionType.ALLOW))) - .collect(Collectors.toList()); + .toList(); } /** diff --git a/src/main/java/com/michelin/ns4kafka/services/executors/ConnectorAsyncExecutor.java b/src/main/java/com/michelin/ns4kafka/services/executors/ConnectorAsyncExecutor.java index 6980234a..1a083820 100644 --- a/src/main/java/com/michelin/ns4kafka/services/executors/ConnectorAsyncExecutor.java +++ b/src/main/java/com/michelin/ns4kafka/services/executors/ConnectorAsyncExecutor.java @@ -12,12 +12,12 @@ import io.micronaut.context.annotation.EachBean; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.http.client.exceptions.ReadTimeoutException; -import io.reactivex.Single; -import io.reactivex.internal.observers.ConsumerSingleObserver; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.internal.observers.ConsumerSingleObserver; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,11 +38,6 @@ public class ConnectorAsyncExecutor { @Inject private ConnectClusterService connectClusterService; - /** - * Constructor - * Required to inject multiple - * @param kafkaAsyncExecutorConfig The managed clusters config - */ public ConnectorAsyncExecutor(KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig) { this.kafkaAsyncExecutorConfig = kafkaAsyncExecutorConfig; } @@ -62,8 +57,7 @@ public void run() { private void synchronizeConnectors() { List selfDeclaredConnectClusterNames = connectClusterService.findAll() .stream() - .map(connectCluster -> connectCluster.getMetadata().getName()) - .collect(Collectors.toList()); + .map(connectCluster -> connectCluster.getMetadata().getName()).toList(); Stream.concat(kafkaAsyncExecutorConfig.getConnects().keySet().stream(), selfDeclaredConnectClusterNames.stream()) .forEach(this::synchronizeConnectCluster); @@ -83,7 +77,7 @@ private void synchronizeConnectCluster(String connectCluster) { List toCreate = ns4kafkaConnectors.stream() .filter(connector -> brokerConnectors.stream().noneMatch(connector1 -> connector1.getMetadata().getName().equals(connector.getMetadata().getName()))) - .collect(Collectors.toList()); + .toList(); List toUpdate = ns4kafkaConnectors.stream() .filter(connector -> brokerConnectors.stream() @@ -93,11 +87,11 @@ private void synchronizeConnectCluster(String connectCluster) { } return false; })) - .collect(Collectors.toList()); + .toList(); List toDelete = brokerConnectors.stream() .filter(connector -> ns4kafkaConnectors.stream().noneMatch(connector1 -> connector1.getMetadata().getName().equals(connector.getMetadata().getName()))) - .collect(Collectors.toList()); + .toList(); if (log.isDebugEnabled()) { toCreate.forEach(connector -> log.debug("Connector to create: " + connector.getMetadata().getName())); @@ -139,7 +133,7 @@ public Single> collectBrokerConnectors(String connectCluster) { .values() .stream() .map(connectorStatus -> buildConnectorFromConnectorStatus(connectorStatus, connectCluster)) - .collect(Collectors.toList()); + .toList(); }); } @@ -171,7 +165,7 @@ private List collectNs4KafkaConnectors(String connectCluster) { List connectorList = connectorRepository.findAllForCluster(kafkaAsyncExecutorConfig.getName()) .stream() .filter(connector -> connector.getSpec().getConnectCluster().equals(connectCluster)) - .collect(Collectors.toList()); + .toList(); log.debug("Connectors found on Ns4kafka for Connect cluster {}: {}", connectCluster, connectorList.size()); return connectorList; } diff --git a/src/main/java/com/michelin/ns4kafka/services/executors/ConsumerGroupAsyncExecutor.java b/src/main/java/com/michelin/ns4kafka/services/executors/ConsumerGroupAsyncExecutor.java index 686eaaf8..a256de8a 100644 --- a/src/main/java/com/michelin/ns4kafka/services/executors/ConsumerGroupAsyncExecutor.java +++ b/src/main/java/com/michelin/ns4kafka/services/executors/ConsumerGroupAsyncExecutor.java @@ -2,6 +2,7 @@ import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; import io.micronaut.context.annotation.EachBean; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.Admin; import org.apache.kafka.clients.admin.ConsumerGroupDescription; @@ -9,7 +10,6 @@ import org.apache.kafka.clients.consumer.OffsetAndMetadata; import org.apache.kafka.common.TopicPartition; -import javax.inject.Singleton; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -22,15 +22,8 @@ @EachBean(KafkaAsyncExecutorConfig.class) @Singleton public class ConsumerGroupAsyncExecutor { - /** - * The managed clusters config - */ private final KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig; - /** - * Constructor - * @param kafkaAsyncExecutorConfig The managed clusters config - */ public ConsumerGroupAsyncExecutor(KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig) { this.kafkaAsyncExecutorConfig = kafkaAsyncExecutorConfig; } @@ -107,7 +100,7 @@ public List getTopicPartitions(String topicName) throws Executio .partitions() .stream() .map(partitionInfo -> new TopicPartition(topicName, partitionInfo.partition())) - .collect(Collectors.toList()); + .toList(); } /** @@ -162,7 +155,7 @@ public Map getLogTimestampOffsets(List par List unsuccessfulPartitions = offsets.entrySet().stream() .filter(e -> e.getValue() == -1L) .map(Map.Entry::getKey) - .collect(Collectors.toList()); + .toList(); // reprocess failed offsets to OffsetSpec.latest() Map reprocessedUnsuccessfulOffsets = getLogEndOffsets(unsuccessfulPartitions); diff --git a/src/main/java/com/michelin/ns4kafka/services/executors/KafkaAsyncExecutorScheduler.java b/src/main/java/com/michelin/ns4kafka/services/executors/KafkaAsyncExecutorScheduler.java index ba124cb2..f4ded84e 100644 --- a/src/main/java/com/michelin/ns4kafka/services/executors/KafkaAsyncExecutorScheduler.java +++ b/src/main/java/com/michelin/ns4kafka/services/executors/KafkaAsyncExecutorScheduler.java @@ -3,10 +3,10 @@ import io.micronaut.runtime.event.ApplicationStartupEvent; import io.micronaut.runtime.event.annotation.EventListener; import io.micronaut.scheduling.annotation.Scheduled; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -14,33 +14,18 @@ @Slf4j @Singleton public class KafkaAsyncExecutorScheduler { - /** - * The topic async executor - */ @Inject List topicAsyncExecutors; - /** - * The ACL async executor - */ @Inject List accessControlEntryAsyncExecutors; - /** - * The connector async executor - */ @Inject List connectorAsyncExecutors; - /** - * The user async executor - */ @Inject List userAsyncExecutors; - /** - * Is the application ready - */ private final AtomicBoolean ready = new AtomicBoolean(false); /** diff --git a/src/main/java/com/michelin/ns4kafka/services/executors/TopicAsyncExecutor.java b/src/main/java/com/michelin/ns4kafka/services/executors/TopicAsyncExecutor.java index 3a6fc014..17cb9bab 100644 --- a/src/main/java/com/michelin/ns4kafka/services/executors/TopicAsyncExecutor.java +++ b/src/main/java/com/michelin/ns4kafka/services/executors/TopicAsyncExecutor.java @@ -6,13 +6,13 @@ import com.michelin.ns4kafka.repositories.TopicRepository; import com.michelin.ns4kafka.repositories.kafka.KafkaStoreException; import io.micronaut.context.annotation.EachBean; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.*; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.config.ConfigResource; -import javax.inject.Inject; -import javax.inject.Singleton; import java.net.MalformedURLException; import java.time.Instant; import java.util.*; @@ -32,7 +32,6 @@ public class TopicAsyncExecutor { @Inject TopicRepository topicRepository; - public TopicAsyncExecutor(KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig) throws MalformedURLException { this.kafkaAsyncExecutorConfig = kafkaAsyncExecutorConfig; } @@ -41,17 +40,16 @@ private Admin getAdminClient(){ return kafkaAsyncExecutorConfig.getAdminClient(); } - //TODO abstract synchronization process to handle different Kafka "models" - // ie : cloud API vs AdminClient + /** + * Start topic synchronization + */ public void run(){ - - // execute topic changes if(this.kafkaAsyncExecutorConfig.isManageTopics()) { synchronizeTopics(); } } - /**** TOPICS MANAGEMENT ***/ - public void synchronizeTopics(){ + + public void synchronizeTopics() { log.debug("Starting topic collection for cluster {}",kafkaAsyncExecutorConfig.getName()); try { // List topics from broker @@ -62,28 +60,29 @@ public void synchronizeTopics(){ // Compute toCreate, toDelete, and toUpdate lists List toCreate = ns4kafkaTopicList.stream() .filter(topic -> !brokerTopicList.containsKey(topic.getMetadata().getName())) - .collect(Collectors.toList()); + .toList(); List toDelete = brokerTopicList.values() .stream() .filter(topic -> ns4kafkaTopicList.stream().noneMatch(topic1 -> topic1.getMetadata().getName().equals(topic.getMetadata().getName()))) - .collect(Collectors.toList()); + .toList(); List toCheckConf = ns4kafkaTopicList.stream() .filter(topic -> brokerTopicList.containsKey(topic.getMetadata().getName())) - .collect(Collectors.toList()); + .toList(); + Map> toUpdate = toCheckConf.stream() .map(topic -> { Map actualConf = brokerTopicList.get(topic.getMetadata().getName()).getSpec().getConfigs(); Map expectedConf = topic.getSpec().getConfigs() == null ? Map.of() : topic.getSpec().getConfigs(); Collection topicConfigChanges = computeConfigChanges(expectedConf,actualConf); - if(topicConfigChanges.size()>0){ + if(!topicConfigChanges.isEmpty()){ ConfigResource cr = new ConfigResource(ConfigResource.Type.TOPIC, topic.getMetadata().getName()); return Map.entry(cr,topicConfigChanges); } return null; }) - .filter(Objects::nonNull) //TODO can we avoid this filter ? + .filter(Objects::nonNull) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if(log.isDebugEnabled()){ @@ -109,8 +108,8 @@ public void synchronizeTopics(){ log.error("Error", e); Thread.currentThread().interrupt(); } - } + private void deleteTopics(List topics) { //TODO What's the best way to prevent delete __consumer_offsets and other internal topics ? // delete only topics that belongs to a namespace and ignore others ? @@ -146,7 +145,7 @@ public List listBrokerTopicNames() throws InterruptedException, Executio .get(30, TimeUnit.SECONDS) .stream() .map(TopicListing::name) - .collect(Collectors.toList()); + .toList(); } public Map collectBrokerTopicsFromNames(List topicNames) throws InterruptedException, ExecutionException, TimeoutException { @@ -156,8 +155,7 @@ public Map collectBrokerTopicsFromNames(List topicNames) return getAdminClient() .describeConfigs(topicNames.stream() .map(s -> new ConfigResource(ConfigResource.Type.TOPIC, s)) - .collect(Collectors.toList()) - ) + .toList()) .all() .get(30, TimeUnit.SECONDS) .entrySet() @@ -189,7 +187,6 @@ public Map collectBrokerTopicsFromNames(List topicNames) private void alterTopics(Map> toUpdate, List topics) { AlterConfigsResult alterConfigsResult = getAdminClient().incrementalAlterConfigs(toUpdate); alterConfigsResult.values().entrySet() - .stream() .forEach(mapEntry -> { Topic updatedTopic = topics.stream().filter(t -> t.getMetadata().getName().equals(mapEntry.getKey().name())).findFirst().get(); try { @@ -212,6 +209,7 @@ private void alterTopics(Map> toUpdate topicRepository.create(updatedTopic); }); } + private void createTopics(List topics) { List newTopics = topics.stream() .map(topic -> { @@ -221,27 +219,26 @@ private void createTopics(List topics) { log.debug("{}",newTopic); return newTopic; }) - .collect(Collectors.toList()); + .toList(); + CreateTopicsResult createTopicsResult = getAdminClient().createTopics(newTopics); - createTopicsResult.values().entrySet() - .stream() - .forEach(mapEntry -> { - Topic createdTopic = topics.stream().filter(t -> t.getMetadata().getName().equals(mapEntry.getKey())).findFirst().get(); - try { - mapEntry.getValue().get(10, TimeUnit.SECONDS); - createdTopic.getMetadata().setCreationTimestamp(Date.from(Instant.now())); - createdTopic.getMetadata().setGeneration(1); - createdTopic.setStatus(Topic.TopicStatus.ofSuccess("Topic created")); - log.info("Success creating topic {} on {}", mapEntry.getKey(),this.kafkaAsyncExecutorConfig.getName()); - } catch (InterruptedException e) { - log.error("Error", e); - Thread.currentThread().interrupt(); - } catch (Exception e) { - createdTopic.setStatus(Topic.TopicStatus.ofFailed("Error while creating topic: "+e.getMessage())); - log.error(String.format("Error while creating topic %s on %s", mapEntry.getKey(),this.kafkaAsyncExecutorConfig.getName()), e); - } - topicRepository.create(createdTopic); - }); + createTopicsResult.values().forEach((key, value) -> { + Topic createdTopic = topics.stream().filter(t -> t.getMetadata().getName().equals(key)).findFirst().get(); + try { + value.get(10, TimeUnit.SECONDS); + createdTopic.getMetadata().setCreationTimestamp(Date.from(Instant.now())); + createdTopic.getMetadata().setGeneration(1); + createdTopic.setStatus(Topic.TopicStatus.ofSuccess("Topic created")); + log.info("Success creating topic {} on {}", key, this.kafkaAsyncExecutorConfig.getName()); + } catch (InterruptedException e) { + log.error("Error", e); + Thread.currentThread().interrupt(); + } catch (Exception e) { + createdTopic.setStatus(Topic.TopicStatus.ofFailed("Error while creating topic: " + e.getMessage())); + log.error(String.format("Error while creating topic %s on %s", key, this.kafkaAsyncExecutorConfig.getName()), e); + } + topicRepository.create(createdTopic); + }); } private Collection computeConfigChanges(Map expected, Map actual){ @@ -249,12 +246,14 @@ private Collection computeConfigChanges(Map expect .stream() .filter(expectedEntry -> !actual.containsKey(expectedEntry.getKey())) .map(expectedEntry -> new AlterConfigOp(new ConfigEntry(expectedEntry.getKey(),expectedEntry.getValue()), AlterConfigOp.OpType.SET)) - .collect(Collectors.toList()); + .toList(); + List toDelete = actual.entrySet() .stream() .filter(actualEntry -> !expected.containsKey(actualEntry.getKey())) .map(expectedEntry -> new AlterConfigOp(new ConfigEntry(expectedEntry.getKey(),expectedEntry.getValue()), AlterConfigOp.OpType.DELETE)) - .collect(Collectors.toList()); + .toList(); + List toChange = expected.entrySet() .stream() .filter(expectedEntry -> { @@ -266,7 +265,7 @@ private Collection computeConfigChanges(Map expect return false; }) .map(expectedEntry -> new AlterConfigOp(new ConfigEntry(expectedEntry.getKey(),expectedEntry.getValue()), AlterConfigOp.OpType.SET)) - .collect(Collectors.toList()); + .toList(); List total = new ArrayList<>(); total.addAll(toCreate); diff --git a/src/main/java/com/michelin/ns4kafka/services/executors/UserAsyncExecutor.java b/src/main/java/com/michelin/ns4kafka/services/executors/UserAsyncExecutor.java index 492bbbea..b07c68a4 100644 --- a/src/main/java/com/michelin/ns4kafka/services/executors/UserAsyncExecutor.java +++ b/src/main/java/com/michelin/ns4kafka/services/executors/UserAsyncExecutor.java @@ -1,9 +1,11 @@ package com.michelin.ns4kafka.services.executors; import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; -import com.michelin.ns4kafka.utils.exceptions.ResourceValidationException; import com.michelin.ns4kafka.repositories.NamespaceRepository; +import com.michelin.ns4kafka.utils.exceptions.ResourceValidationException; import io.micronaut.context.annotation.EachBean; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.Admin; import org.apache.kafka.clients.admin.ScramCredentialInfo; @@ -14,8 +16,6 @@ import org.apache.kafka.common.quota.ClientQuotaFilter; import org.apache.kafka.common.quota.ClientQuotaFilterComponent; -import javax.inject.Inject; -import javax.inject.Singleton; import java.security.SecureRandom; import java.util.Base64; import java.util.List; @@ -27,8 +27,8 @@ @EachBean(KafkaAsyncExecutorConfig.class) @Singleton public class UserAsyncExecutor { - private final KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig; + private final AbstractUserSynchronizer userExecutor; @Inject @@ -77,15 +77,13 @@ public void synchronizeUsers() { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (log.isDebugEnabled()) { - log.debug("UserQuotas to create : " + toCreate.keySet().stream().collect(Collectors.joining(", "))); + log.debug("UserQuotas to create : " + String.join(", ", toCreate.keySet())); log.debug("UserQuotas to delete : " + toDelete.size()); log.debug("UserQuotas to update : " + toUpdate.size()); } createUserQuotas(toCreate); - deleteUserQuotas(toDelete); createUserQuotas(toUpdate); - } public String resetPassword(String user) { @@ -115,10 +113,6 @@ private void createUserQuotas(Map> toCreate) { toCreate.forEach(this.userExecutor::applyQuotas); } - private void deleteUserQuotas(Map> toDelete) { - // Not deleting quotas that could impact users not managed by ns4kafka. - } - interface AbstractUserSynchronizer { boolean canSynchronizeQuotas(); @@ -193,9 +187,9 @@ public Map> listQuotas() { @Override public void applyQuotas(String user, Map quotas) { ClientQuotaEntity client = new ClientQuotaEntity(Map.of("user", user)); - ClientQuotaAlteration.Op producer_quota = new ClientQuotaAlteration.Op("producer_byte_rate", 102400.0); - ClientQuotaAlteration.Op consumer_quota = new ClientQuotaAlteration.Op("consumer_byte_rate", 102400.0); - ClientQuotaAlteration clientQuota = new ClientQuotaAlteration(client, List.of(producer_quota, consumer_quota)); + ClientQuotaAlteration.Op producerQuota = new ClientQuotaAlteration.Op("producer_byte_rate", 102400.0); + ClientQuotaAlteration.Op consumerQuota = new ClientQuotaAlteration.Op("consumer_byte_rate", 102400.0); + ClientQuotaAlteration clientQuota = new ClientQuotaAlteration(client, List.of(producerQuota, consumerQuota)); try { admin.alterClientQuotas(List.of(clientQuota)).all().get(10, TimeUnit.SECONDS); log.info("Success applying quotas {} for user {}", quotas, user); diff --git a/src/main/java/com/michelin/ns4kafka/services/schema/KafkaSchemaRegistryClientProxy.java b/src/main/java/com/michelin/ns4kafka/services/schema/KafkaSchemaRegistryClientProxy.java index 891ccc0a..ff3df0a6 100644 --- a/src/main/java/com/michelin/ns4kafka/services/schema/KafkaSchemaRegistryClientProxy.java +++ b/src/main/java/com/michelin/ns4kafka/services/schema/KafkaSchemaRegistryClientProxy.java @@ -1,7 +1,7 @@ package com.michelin.ns4kafka.services.schema; -import com.michelin.ns4kafka.utils.exceptions.ResourceValidationException; import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; +import com.michelin.ns4kafka.utils.exceptions.ResourceValidationException; import io.micronaut.core.async.publisher.Publishers; import io.micronaut.core.util.StringUtils; import io.micronaut.http.HttpHeaders; @@ -10,48 +10,29 @@ import io.micronaut.http.MutableHttpResponse; import io.micronaut.http.annotation.Filter; import io.micronaut.http.client.ProxyHttpClient; -import io.micronaut.http.filter.OncePerRequestHttpServerFilter; +import io.micronaut.http.filter.HttpServerFilter; import io.micronaut.http.filter.ServerFilterChain; +import jakarta.inject.Inject; import org.reactivestreams.Publisher; -import javax.inject.Inject; import java.net.URI; import java.util.List; import java.util.Optional; import java.util.UUID; @Filter(KafkaSchemaRegistryClientProxy.SCHEMA_REGISTRY_PREFIX + "/**") -public class KafkaSchemaRegistryClientProxy extends OncePerRequestHttpServerFilter { - /** - * Schema registry prefix used to filter request to schema registries. It'll be replaced by - * the schema registry URL of the given cluster - */ +public class KafkaSchemaRegistryClientProxy implements HttpServerFilter { public static final String SCHEMA_REGISTRY_PREFIX = "/schema-registry-proxy"; - /** - * A header that contains the Kafka cluster - */ public static final String PROXY_HEADER_KAFKA_CLUSTER = "X-Kafka-Cluster"; - /** - * A header that contains a secret for the request - */ public static final String PROXY_HEADER_SECRET = "X-Proxy-Secret"; - /** - * Generate a secret - */ public static final String PROXY_SECRET = UUID.randomUUID().toString(); - /** - * Managed clusters configuration - */ @Inject List kafkaAsyncExecutorConfigs; - /** - * HTTP client - */ @Inject ProxyHttpClient client; @@ -62,7 +43,7 @@ public class KafkaSchemaRegistryClientProxy extends OncePerRequestHttpServerFilt * @return A modified request */ @Override - public Publisher> doFilterOnce(HttpRequest request, ServerFilterChain chain) { + public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { // Check call is initiated from Micronaut and not from outside if (!request.getHeaders().contains(KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET)) { return Publishers.just(new ResourceValidationException(List.of("Missing required header " + KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET), null, null)); diff --git a/src/main/java/com/michelin/ns4kafka/services/schema/client/KafkaSchemaRegistryClient.java b/src/main/java/com/michelin/ns4kafka/services/schema/client/KafkaSchemaRegistryClient.java index bdc1a73a..4835ac98 100644 --- a/src/main/java/com/michelin/ns4kafka/services/schema/client/KafkaSchemaRegistryClient.java +++ b/src/main/java/com/michelin/ns4kafka/services/schema/client/KafkaSchemaRegistryClient.java @@ -7,8 +7,8 @@ import com.michelin.ns4kafka.services.schema.client.entities.SchemaResponse; import io.micronaut.http.annotation.*; import io.micronaut.http.client.annotation.Client; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; import java.util.List; @@ -16,12 +16,12 @@ public interface KafkaSchemaRegistryClient { @Get("/subjects") Single> getSubjects(@Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET) String secret, - @Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_KAFKA_CLUSTER) String cluster); + @Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_KAFKA_CLUSTER) String cluster); @Get("/subjects/{subject}/versions/latest") Maybe getLatestSubject(@Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET) String secret, - @Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_KAFKA_CLUSTER) String cluster, - @PathVariable String subject); + @Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_KAFKA_CLUSTER) String cluster, + @PathVariable String subject); @Post("/subjects/{subject}/versions") Single register(@Header(value = KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET) String secret, diff --git a/src/main/java/com/michelin/ns4kafka/utils/BytesUtils.java b/src/main/java/com/michelin/ns4kafka/utils/BytesUtils.java index 21d240f4..4c802d25 100644 --- a/src/main/java/com/michelin/ns4kafka/utils/BytesUtils.java +++ b/src/main/java/com/michelin/ns4kafka/utils/BytesUtils.java @@ -1,7 +1,5 @@ package com.michelin.ns4kafka.utils; -import org.apache.commons.lang3.StringUtils; - import java.math.BigDecimal; import java.math.RoundingMode; @@ -47,24 +45,24 @@ public static long humanReadableToBytes(String quota) { long gibibyte = mebibyte * 1024; if (quota.endsWith(KIBIBYTE)) { - return BigDecimal.valueOf(Double.parseDouble(quota.replace(KIBIBYTE, StringUtils.EMPTY)) * kibibyte) + return BigDecimal.valueOf(Double.parseDouble(quota.replace(KIBIBYTE, "")) * kibibyte) .setScale(0, RoundingMode.CEILING) .longValue(); } if (quota.endsWith(MEBIBYTE)) { - return BigDecimal.valueOf(Double.parseDouble(quota.replace(MEBIBYTE, StringUtils.EMPTY)) * mebibyte) + return BigDecimal.valueOf(Double.parseDouble(quota.replace(MEBIBYTE, "")) * mebibyte) .setScale(0, RoundingMode.CEILING) .longValue(); } if (quota.endsWith(GIBIBYTE)) { - return BigDecimal.valueOf(Double.parseDouble(quota.replace(GIBIBYTE, StringUtils.EMPTY)) * gibibyte) + return BigDecimal.valueOf(Double.parseDouble(quota.replace(GIBIBYTE, "")) * gibibyte) .setScale(0, RoundingMode.CEILING) .longValue(); } - return Long.parseLong(quota.replace(BYTE, StringUtils.EMPTY)); + return Long.parseLong(quota.replace(BYTE, "")); } private BytesUtils() {} diff --git a/src/main/java/com/michelin/ns4kafka/utils/EncryptionUtils.java b/src/main/java/com/michelin/ns4kafka/utils/EncryptionUtils.java index 9be98ed8..fb9c8ff3 100644 --- a/src/main/java/com/michelin/ns4kafka/utils/EncryptionUtils.java +++ b/src/main/java/com/michelin/ns4kafka/utils/EncryptionUtils.java @@ -4,8 +4,8 @@ import com.nimbusds.jose.crypto.AESDecrypter; import com.nimbusds.jose.crypto.AESEncrypter; import com.nimbusds.jose.util.Base64URL; +import io.micronaut.core.util.StringUtils; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -14,9 +14,6 @@ @Slf4j public class EncryptionUtils { - /** - * Constructor - */ private EncryptionUtils() { } /** @@ -27,7 +24,7 @@ private EncryptionUtils() { } */ public static String encryptAES256GCM(String clearText, String key) { try { - if (!StringUtils.isNotBlank(clearText)) { + if (!StringUtils.hasText(clearText)) { return clearText; } @@ -57,7 +54,7 @@ public static String encryptAES256GCM(String clearText, String key) { */ public static String decryptAES256GCM(String encryptedText, String key) { try { - if (!StringUtils.isNotBlank(encryptedText)) { + if (!StringUtils.hasText(encryptedText)) { return encryptedText; } diff --git a/src/main/java/com/michelin/ns4kafka/validation/ResourceValidator.java b/src/main/java/com/michelin/ns4kafka/validation/ResourceValidator.java index f53fbe94..4a97d244 100644 --- a/src/main/java/com/michelin/ns4kafka/validation/ResourceValidator.java +++ b/src/main/java/com/michelin/ns4kafka/validation/ResourceValidator.java @@ -140,7 +140,7 @@ public void ensureValid(final String name, final Object o) { List values = List.of(s); //default if no "," (most of the time) if(s.contains(",")){ //split and strip - values = Arrays.stream(s.split(",")).map(item -> item.strip()).collect(Collectors.toList()); + values = Arrays.stream(s.split(",")).map(String::strip).toList(); } for (String string : values) { diff --git a/src/test/java/com/michelin/ns4kafka/controllers/AccessControlListControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/AccessControlListControllerTest.java index 432e326e..6f8be795 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/AccessControlListControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/AccessControlListControllerTest.java @@ -12,7 +12,6 @@ import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.security.authentication.Authentication; -import io.micronaut.security.authentication.DefaultAuthentication; import io.micronaut.security.utils.SecurityService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -32,33 +31,18 @@ @ExtendWith(MockitoExtension.class) class AccessControlListControllerTest { - /** - * The mocked ACL service - */ @Mock AccessControlEntryService accessControlEntryService; - /** - * The mocked namespace service - */ @Mock NamespaceService namespaceService; - /** - * The mocked app event publisher - */ @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; - /** - * The mocked security service - */ @Mock SecurityService securityService; - /** - * The mocked ACL controller - */ @InjectMocks AccessControlListController accessControlListController; @@ -273,7 +257,7 @@ void applyAsAdmin_Failure() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("admin", Map.of("roles",List.of("isAdmin()"))); + Authentication auth = Authentication.build("admin", Map.of("roles",List.of("isAdmin()"))); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -284,6 +268,7 @@ void applyAsAdmin_Failure() { () -> accessControlListController.apply(auth,"test", ace1, false)); Assertions.assertEquals(1, actual.getValidationErrors().size()); } + @Test void applyAsAdmin_Success() { Namespace ns = Namespace.builder() @@ -300,7 +285,7 @@ void applyAsAdmin_Success() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("admin", Map.of("roles",List.of("isAdmin()"))); + Authentication auth = Authentication.build("admin", Map.of("roles",List.of("isAdmin()"))); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -332,7 +317,7 @@ void applyValidationErrors() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -360,7 +345,7 @@ void applySuccess() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -394,7 +379,7 @@ void applySuccess_AlreadyExists() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -438,7 +423,7 @@ void applyFailed_ChangedSpec() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -484,7 +469,7 @@ void applySuccess_ChangedMetadata() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -534,7 +519,7 @@ void applySuccess_ChangedMetadataDryRun() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -568,7 +553,7 @@ void applyDryRunAdmin() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("admin", Map.of("roles",List.of("isAdmin()"))); + Authentication auth = Authentication.build("admin", Map.of("roles",List.of("isAdmin()"))); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -598,7 +583,7 @@ void applyDryRun() { .build()) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -620,7 +605,7 @@ void deleteFailNotFound() { .metadata(ObjectMeta.builder().name("test").cluster("local").build()) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -653,7 +638,7 @@ void deleteFailSelfAssigned() { .build()) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -688,14 +673,14 @@ void deleteSuccessSelfAssigned_AsAdmin() { .build()) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of("isAdmin()"))); + Authentication auth = Authentication.build("user", Map.of("roles",List.of("isAdmin()"))); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); Mockito.when(accessControlEntryService.findByName("test", "ace1")) .thenReturn(Optional.of(ace1)); - HttpResponse actual = accessControlListController.delete(auth,"test", "ace1", false); + HttpResponse actual = accessControlListController.delete(auth,"test", "ace1", false); Assertions.assertEquals(HttpStatus.NO_CONTENT, actual.status()); } @@ -719,7 +704,7 @@ void deleteSuccess() { .grantedTo("namespace-other") .build()) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); @@ -729,7 +714,7 @@ void deleteSuccess() { when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); - HttpResponse actual = accessControlListController.delete(auth,"test", "ace1", false); + HttpResponse actual = accessControlListController.delete(auth,"test", "ace1", false); Assertions.assertEquals(HttpStatus.NO_CONTENT, actual.status()); } @@ -754,13 +739,13 @@ void deleteDryRun() { .build() ) .build(); - Authentication auth = new DefaultAuthentication("user", Map.of("roles",List.of())); + Authentication auth = Authentication.build("user", Map.of("roles",List.of())); Mockito.when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); Mockito.when(accessControlEntryService.findByName("test", "ace1")) .thenReturn(Optional.of(ace1)); - HttpResponse actual = accessControlListController.delete(auth,"test", "ace1", true); + HttpResponse actual = accessControlListController.delete(auth,"test", "ace1", true); verify(accessControlEntryService, never()).delete(any(), any()); Assertions.assertEquals(HttpStatus.NO_CONTENT, actual.status()); diff --git a/src/test/java/com/michelin/ns4kafka/controllers/ConnectClusterControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/ConnectClusterControllerTest.java index 72ab79da..4da263e5 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/ConnectClusterControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/ConnectClusterControllerTest.java @@ -48,7 +48,7 @@ class ConnectClusterControllerTest { ConnectClusterController connectClusterController; @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; /** * Test connect clusters listing when namespace is empty diff --git a/src/test/java/com/michelin/ns4kafka/controllers/ConnectorControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/ConnectorControllerTest.java index 87266964..6560e66c 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/ConnectorControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/ConnectorControllerTest.java @@ -1,9 +1,9 @@ package com.michelin.ns4kafka.controllers; -import com.michelin.ns4kafka.models.connector.ChangeConnectorState; -import com.michelin.ns4kafka.models.connector.Connector; import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.models.ObjectMeta; +import com.michelin.ns4kafka.models.connector.ChangeConnectorState; +import com.michelin.ns4kafka.models.connector.Connector; import com.michelin.ns4kafka.security.ResourceBasedSecurityRule; import com.michelin.ns4kafka.services.ConnectorService; import com.michelin.ns4kafka.services.NamespaceService; @@ -14,7 +14,7 @@ import io.micronaut.http.HttpStatus; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.security.utils.SecurityService; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Single; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,40 +31,21 @@ @ExtendWith(MockitoExtension.class) class ConnectorControllerTest { - /** - * Connector service - */ @Mock ConnectorService connectorService; - /** - * Namespace service - */ @Mock NamespaceService namespaceService; - /** - * App service - */ @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; - /** - * Security service - */ @Mock SecurityService securityService; - /** - * Connector controller - */ @InjectMocks ConnectorController connectorController; - - /** - * The mocked resource quota service - */ @Mock ResourceQuotaService resourceQuotaService; diff --git a/src/test/java/com/michelin/ns4kafka/controllers/ConsumerGroupControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/ConsumerGroupControllerTest.java index 4612a8ee..0ab1b4f4 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/ConsumerGroupControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/ConsumerGroupControllerTest.java @@ -32,33 +32,18 @@ @ExtendWith(MockitoExtension.class) class ConsumerGroupControllerTest { - /** - * The mocked namespace service - */ @Mock NamespaceService namespaceService; - /** - * The mocked consumer group service - */ @Mock ConsumerGroupService consumerGroupService; - /** - * The mocked app event publisher - */ @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; - /** - * The mocked security service - */ @Mock SecurityService securityService; - /** - * The mocked consumer group controller - */ @InjectMocks ConsumerGroupController consumerGroupController; diff --git a/src/test/java/com/michelin/ns4kafka/controllers/ExceptionHandlerControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/ExceptionHandlerControllerTest.java index b95da542..c8ad6c5d 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/ExceptionHandlerControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/ExceptionHandlerControllerTest.java @@ -4,9 +4,9 @@ import io.micronaut.http.HttpMethod; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpStatus; +import io.micronaut.security.authentication.Authentication; import io.micronaut.security.authentication.AuthenticationException; import io.micronaut.security.authentication.AuthorizationException; -import io.micronaut.security.authentication.DefaultAuthentication; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -21,8 +21,8 @@ class ExceptionHandlerControllerTest { @Test void resourceValidationError() { - var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local") - ,new ResourceValidationException(List.of("Error1", "Error2"),"Topic", "Name")); + var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local"), + new ResourceValidationException(List.of("Error1", "Error2"),"Topic", "Name")); var status = response.body(); Assertions.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); @@ -36,8 +36,8 @@ void resourceValidationError() { @Test void constraintViolationError() { - var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local") - ,new ConstraintViolationException(Set.of())); + var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local"), + new ConstraintViolationException(Set.of())); var status = response.body(); Assertions.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); @@ -46,8 +46,8 @@ void constraintViolationError() { @Test void authorizationUnauthorizedError() { - var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local") - ,new AuthorizationException(null)); + var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local"), + new AuthorizationException(null)); var status = response.body(); Assertions.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatus()); @@ -56,8 +56,8 @@ void authorizationUnauthorizedError() { @Test void authorizationForbiddenError() { - var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local") - ,new AuthorizationException(new DefaultAuthentication("user", Map.of()))); + var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local"), + new AuthorizationException(Authentication.build("user", Map.of()))); var status = response.body(); Assertions.assertEquals(HttpStatus.FORBIDDEN, response.getStatus()); @@ -66,8 +66,8 @@ void authorizationForbiddenError() { @Test void authenticationError() { - var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local") - ,new AuthenticationException()); + var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local"), + new AuthenticationException()); var status = response.body(); Assertions.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatus()); @@ -76,12 +76,11 @@ void authenticationError() { @Test void anyError() { - var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local") - ,new Exception()); + var response = exceptionHandlerController.error(HttpRequest.create(HttpMethod.POST, "local"), + new Exception()); var status = response.body(); Assertions.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); Assertions.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), status.getCode()); } - } diff --git a/src/test/java/com/michelin/ns4kafka/controllers/NamespaceControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/NamespaceControllerTest.java index 9203049f..9969f0eb 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/NamespaceControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/NamespaceControllerTest.java @@ -26,11 +26,13 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -public class NamespaceControllerTest { +class NamespaceControllerTest { @Mock NamespaceService namespaceService; + @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; + @Mock SecurityService securityService; diff --git a/src/test/java/com/michelin/ns4kafka/controllers/ResourceQuotaControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/ResourceQuotaControllerTest.java index d5abcbbc..b9063cc5 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/ResourceQuotaControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/ResourceQuotaControllerTest.java @@ -43,7 +43,7 @@ class ResourceQuotaControllerTest { SecurityService securityService; @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; /** * Validate quota listing diff --git a/src/test/java/com/michelin/ns4kafka/controllers/RoleBindingControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/RoleBindingControllerTest.java index 90ff3faa..9883ae32 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/RoleBindingControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/RoleBindingControllerTest.java @@ -24,14 +24,16 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -public class RoleBindingControllerTest { - +class RoleBindingControllerTest { @Mock NamespaceService namespaceService; + @Mock RoleBindingService roleBindingService; + @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; + @Mock SecurityService securityService; diff --git a/src/test/java/com/michelin/ns4kafka/controllers/SchemaControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/SchemaControllerTest.java index e094982b..036328ef 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/SchemaControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/SchemaControllerTest.java @@ -12,8 +12,8 @@ import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.http.HttpStatus; import io.micronaut.security.utils.SecurityService; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -28,35 +28,20 @@ @ExtendWith(MockitoExtension.class) class SchemaControllerTest { - /** - * Namespace service - */ @Mock NamespaceService namespaceService; - /** - * Schema service - */ @Mock SchemaService schemaService; - /** - * Schema controller - */ @InjectMocks SchemaController schemaController; - /** - * Security service - */ @Mock SecurityService securityService; - /** - * App publisher - */ @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; /** * Test the schema creation diff --git a/src/test/java/com/michelin/ns4kafka/controllers/StreamControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/StreamControllerTest.java index c8b5c73c..f6539660 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/StreamControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/StreamControllerTest.java @@ -26,33 +26,18 @@ @ExtendWith(MockitoExtension.class) class StreamControllerTest { - /** - * The mocked namespace service - */ @Mock NamespaceService namespaceService; - /** - * The mocked stream service - */ @Mock StreamService streamService; - /** - * The mocked app event publisher - */ @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; - /** - * The mocked security service - */ @Mock SecurityService securityService; - /** - * The mocked Kafka Streams controller - */ @InjectMocks StreamController streamController; diff --git a/src/test/java/com/michelin/ns4kafka/controllers/TopicControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/TopicControllerTest.java index e88f03e7..9a6015b2 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/TopicControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/TopicControllerTest.java @@ -46,7 +46,7 @@ class TopicControllerTest { TopicService topicService; @Mock - ApplicationEventPublisher applicationEventPublisher; + ApplicationEventPublisher applicationEventPublisher; @Mock SecurityService securityService; diff --git a/src/test/java/com/michelin/ns4kafka/controllers/TopicNonNamespacedControllerTest.java b/src/test/java/com/michelin/ns4kafka/controllers/TopicNonNamespacedControllerTest.java index 3dcb81ce..f1744781 100644 --- a/src/test/java/com/michelin/ns4kafka/controllers/TopicNonNamespacedControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controllers/TopicNonNamespacedControllerTest.java @@ -3,7 +3,6 @@ import com.michelin.ns4kafka.controllers.topic.TopicNonNamespacedController; import com.michelin.ns4kafka.models.ObjectMeta; import com.michelin.ns4kafka.models.Topic; -import com.michelin.ns4kafka.services.NamespaceService; import com.michelin.ns4kafka.services.TopicService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -18,9 +17,6 @@ @ExtendWith(MockitoExtension.class) class TopicNonNamespacedControllerTest { - @Mock - NamespaceService namespaceService; - @Mock TopicService topicService; diff --git a/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationConnectTest.java b/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationConnectTest.java index 734000df..15c21b74 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationConnectTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationConnectTest.java @@ -1,6 +1,7 @@ package com.michelin.ns4kafka.integration; import com.michelin.ns4kafka.testcontainers.KafkaConnectContainer; +import io.micronaut.core.annotation.NonNull; import org.junit.jupiter.api.TestInstance; import org.testcontainers.utility.DockerImageName; @@ -9,17 +10,17 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class AbstractIntegrationConnectTest extends AbstractIntegrationTest { - public KafkaConnectContainer connect; + /** + * Starts the Kafka Connect container + * @return Properties enriched with the Kafka Connect URL + */ + @NonNull @Override public Map getProperties() { - Map properties = new HashMap<>(); Map brokerProps = super.getProperties(); - if (connect == null || !connect.isRunning()) { - //registry = new SchemaRegistryContainer(DockerImageName.parse("confluentinc/cp-schema-registry:" + CONFLUENT_VERSION), "kafka:9092"); - //registry.start(); connect = new KafkaConnectContainer(DockerImageName.parse("confluentinc/cp-kafka-connect:" + CONFLUENT_VERSION), "kafka:9092") .withEnv("CONNECT_SASL_MECHANISM", "PLAIN") @@ -28,7 +29,8 @@ public Map getProperties() { .withNetwork(network); connect.start(); } - properties.putAll(brokerProps); + + Map properties = new HashMap<>(brokerProps); properties.put("ns4kafka.managed-clusters.test-cluster.connects.test-connect.url", connect.getUrl()); return properties; } diff --git a/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationSchemaRegistryTest.java b/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationSchemaRegistryTest.java index 596eca27..c7f805fa 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationSchemaRegistryTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationSchemaRegistryTest.java @@ -10,26 +10,18 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class AbstractIntegrationSchemaRegistryTest extends AbstractIntegrationTest { - /** - * Version of the Schema Registry Docker image - */ public static final String CONFLUENT_REGISTRY_VERSION = "7.0.0"; - /** - * Schema registry container - */ public SchemaRegistryContainer schemaRegistryContainer; /** * Starts the Schema registry container - * * @return Properties enriched with the Schema Registry URL */ @NonNull @Override public Map getProperties() { Map brokerProps = super.getProperties(); - if (schemaRegistryContainer == null || !schemaRegistryContainer.isRunning()) { schemaRegistryContainer = new SchemaRegistryContainer(DockerImageName.parse("confluentinc/cp-schema-registry:" + CONFLUENT_REGISTRY_VERSION), "kafka:9092") diff --git a/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationTest.java b/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationTest.java index d5b2be7d..f48d21c0 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/AbstractIntegrationTest.java @@ -12,30 +12,12 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class AbstractIntegrationTest implements TestPropertyProvider { - /** - * The Confluent Platform version for the broker image - */ public static final String CONFLUENT_VERSION = "6.2.0"; - /** - * Container for Kafka broker - */ public KafkaContainer kafka; - - /** - * Container network - */ public Network network; - - /** - * Kafka admin client - */ private Admin adminClient; - /** - * Start Kafka Broker container - * @return A map of Kafka Broker container properties - */ @NonNull @Override public Map getProperties() { @@ -85,10 +67,6 @@ public Map getProperties() { ); } - /** - * Getter for admin client - * @return The admin client - */ public Admin getAdminClient() { if (adminClient == null) adminClient = Admin.create(Map.of( diff --git a/src/test/java/com/michelin/ns4kafka/integration/AccessControlListTest.java b/src/test/java/com/michelin/ns4kafka/integration/AccessControlListTest.java index 7041eb5c..731602dd 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/AccessControlListTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/AccessControlListTest.java @@ -15,10 +15,11 @@ import io.micronaut.http.HttpMethod; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.apache.kafka.clients.admin.Admin; import org.apache.kafka.common.acl.*; import org.apache.kafka.common.resource.PatternType; @@ -28,7 +29,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -37,22 +37,13 @@ @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") class AccessControlListTest extends AbstractIntegrationTest { - /** - * The HTTP client - */ @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; - /** - * The ACL executors - */ @Inject List accessControlEntryAsyncExecutorList; - /** - * The authentication token - */ private String token; /** diff --git a/src/test/java/com/michelin/ns4kafka/integration/ApiResourcesTest.java b/src/test/java/com/michelin/ns4kafka/integration/ApiResourcesTest.java index b36a6a91..692065e5 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/ApiResourcesTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/ApiResourcesTest.java @@ -10,14 +10,14 @@ import io.micronaut.http.HttpMethod; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.util.List; @MicronautTest @@ -25,7 +25,7 @@ class ApiResourcesTest extends AbstractIntegrationTest { @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; @Test void asAdmin() { diff --git a/src/test/java/com/michelin/ns4kafka/integration/ConnectTest.java b/src/test/java/com/michelin/ns4kafka/integration/ConnectTest.java index 1baa0492..86d31590 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/ConnectTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/ConnectTest.java @@ -10,7 +10,10 @@ import com.michelin.ns4kafka.models.RoleBinding.*; import com.michelin.ns4kafka.models.connector.ChangeConnectorState; import com.michelin.ns4kafka.models.connector.Connector; -import com.michelin.ns4kafka.services.connect.client.entities.*; +import com.michelin.ns4kafka.services.connect.client.entities.ConnectorInfo; +import com.michelin.ns4kafka.services.connect.client.entities.ConnectorSpecs; +import com.michelin.ns4kafka.services.connect.client.entities.ConnectorStateInfo; +import com.michelin.ns4kafka.services.connect.client.entities.ServerInfo; import com.michelin.ns4kafka.services.executors.ConnectorAsyncExecutor; import com.michelin.ns4kafka.services.executors.TopicAsyncExecutor; import com.michelin.ns4kafka.validation.ConnectValidator; @@ -20,15 +23,15 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; @@ -38,33 +41,18 @@ @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") class ConnectTest extends AbstractIntegrationConnectTest { - /** - * The HTTP client - */ @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; - /** - * The topic async executor list - */ @Inject List topicAsyncExecutorList; - /** - * The connector async executor list - */ @Inject List connectorAsyncExecutorList; - /** - * The authentication token - */ private String token; - /** - * Init all integration tests - */ @BeforeAll void init() { Namespace ns1 = Namespace.builder() @@ -146,7 +134,7 @@ void init() { */ @Test void createConnect() throws MalformedURLException { - RxHttpClient connectCli = RxHttpClient.create(new URL(connect.getUrl())); + Rx3HttpClient connectCli = Rx3HttpClient.create(new URL(connect.getUrl())); ServerInfo actual = connectCli.retrieve(HttpRequest.GET("/"), ServerInfo.class).blockingFirst(); Assertions.assertEquals("6.2.0-ccs", actual.version()); } @@ -252,7 +240,7 @@ void deployConnectors() throws InterruptedException, MalformedURLException { connectorAsyncExecutorList.forEach(ConnectorAsyncExecutor::run); Thread.sleep(2000); - RxHttpClient connectCli = RxHttpClient.create(new URL(connect.getUrl())); + Rx3HttpClient connectCli = Rx3HttpClient.create(new URL(connect.getUrl())); ConnectorInfo actualConnectorWithNullParameter = connectCli.retrieve(HttpRequest.GET("/connectors/ns1-connectorWithNullParameter"), ConnectorInfo.class).blockingFirst(); ConnectorInfo actualConnectorWithEmptyParameter = connectCli.retrieve(HttpRequest.GET("/connectors/ns1-connectorWithEmptyParameter"), ConnectorInfo.class).blockingFirst(); ConnectorInfo actualConnectorWithFillParameter = connectCli.retrieve(HttpRequest.GET("/connectors/ns1-connectorWithFillParameter"), ConnectorInfo.class).blockingFirst(); @@ -317,7 +305,7 @@ void updateConnectorsWithNullProperty() throws InterruptedException, MalformedUR .build()) .build(); - RxHttpClient connectCli = RxHttpClient.create(new URL(connect.getUrl())); + Rx3HttpClient connectCli = Rx3HttpClient.create(new URL(connect.getUrl())); HttpResponse connectorInfo = connectCli.exchange(HttpRequest.PUT("/connectors/ns1-connector/config", connectorSpecs), ConnectorInfo.class).blockingFirst(); // "File" property is present and fill @@ -394,9 +382,8 @@ void restartConnector() throws InterruptedException { * @throws InterruptedException Any interrupted exception */ @Test - void PauseAndResumeConnector() throws MalformedURLException, InterruptedException { - - RxHttpClient connectCli = RxHttpClient.create(new URL(connect.getUrl())); + void pauseAndResumeConnector() throws MalformedURLException, InterruptedException { + Rx3HttpClient connectCli = Rx3HttpClient.create(new URL(connect.getUrl())); Topic to = Topic.builder() .metadata(ObjectMeta.builder() .name("ns1-to1") diff --git a/src/test/java/com/michelin/ns4kafka/integration/ExceptionHandlerTest.java b/src/test/java/com/michelin/ns4kafka/integration/ExceptionHandlerTest.java index a02ac09d..9138bbe9 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/ExceptionHandlerTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/ExceptionHandlerTest.java @@ -15,27 +15,26 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") -public class ExceptionHandlerTest extends AbstractIntegrationTest { - +class ExceptionHandlerTest extends AbstractIntegrationTest { @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; private String token; @@ -84,7 +83,6 @@ void init(){ .build()) .build(); - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin","admin"); HttpResponse response = client.exchange(HttpRequest.POST("/login", credentials), BearerAccessRefreshToken.class).blockingFirst(); @@ -96,8 +94,7 @@ void init(){ } @Test - void invalidTopicName() throws InterruptedException, ExecutionException { - + void invalidTopicName() { Topic topicFirstCreate = Topic.builder() .metadata(ObjectMeta.builder() .name("ns1-invalid-é") @@ -123,7 +120,7 @@ void invalidTopicName() throws InterruptedException, ExecutionException { } @Test - void forbiddenTopic() throws InterruptedException, ExecutionException { + void forbiddenTopic() { HttpClientResponseException exception = Assertions.assertThrows(HttpClientResponseException.class, () -> client.exchange(HttpRequest.create(HttpMethod.GET,"/api/namespaces/ns2/topics") .bearerAuth(token)) @@ -134,19 +131,17 @@ void forbiddenTopic() throws InterruptedException, ExecutionException { } @Test - void UnauthorizedTopic() throws InterruptedException, ExecutionException { - + void UnauthorizedTopic() { HttpClientResponseException exception = Assertions.assertThrows(HttpClientResponseException.class, () -> client.exchange(HttpRequest.create(HttpMethod.GET,"/api/namespaces/ns1/topics")) .blockingFirst()); Assertions.assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatus()); - Assertions.assertEquals("Unauthorized", exception.getMessage()); + Assertions.assertEquals("Client '/': Unauthorized", exception.getMessage()); } @Test - void notFoundTopic() throws InterruptedException, ExecutionException { - + void notFoundTopic() { HttpClientResponseException exception = Assertions.assertThrows(HttpClientResponseException.class, () -> client.exchange(HttpRequest.create(HttpMethod.GET,"/api/namespaces/ns1/topics/not-found-topic") .bearerAuth(token)) @@ -157,8 +152,7 @@ void notFoundTopic() throws InterruptedException, ExecutionException { } @Test - void notValidMethodTopic() throws InterruptedException, ExecutionException { - + void notValidMethodTopic() { HttpClientResponseException exception = Assertions.assertThrows(HttpClientResponseException.class, () -> client.exchange(HttpRequest.create(HttpMethod.PUT,"/api/namespaces/ns1/topics/") .bearerAuth(token)) diff --git a/src/test/java/com/michelin/ns4kafka/integration/LoginTest.java b/src/test/java/com/michelin/ns4kafka/integration/LoginTest.java index 06068552..bbe716f3 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/LoginTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/LoginTest.java @@ -4,22 +4,21 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import javax.inject.Inject; - @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") public class LoginTest extends AbstractIntegrationTest { - @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; + @Test void login(){ diff --git a/src/test/java/com/michelin/ns4kafka/integration/SchemaTest.java b/src/test/java/com/michelin/ns4kafka/integration/SchemaTest.java index 6ac2020d..850febbe 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/SchemaTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/SchemaTest.java @@ -1,7 +1,10 @@ package com.michelin.ns4kafka.integration; import com.michelin.ns4kafka.integration.TopicTest.BearerAccessRefreshToken; -import com.michelin.ns4kafka.models.*; +import com.michelin.ns4kafka.models.AccessControlEntry; +import com.michelin.ns4kafka.models.Namespace; +import com.michelin.ns4kafka.models.ObjectMeta; +import com.michelin.ns4kafka.models.RoleBinding; import com.michelin.ns4kafka.models.schema.Schema; import com.michelin.ns4kafka.models.schema.SchemaCompatibilityState; import com.michelin.ns4kafka.services.schema.client.entities.SchemaCompatibilityResponse; @@ -12,16 +15,16 @@ import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.http.client.HttpClient; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.net.MalformedURLException; import java.net.URL; import java.util.List; @@ -30,21 +33,12 @@ @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") class SchemaTest extends AbstractIntegrationSchemaRegistryTest { - /** - * The HTTP client - */ @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; - /** - * The Schema Registry client - */ HttpClient schemaClient; - /** - * The authentication token - */ private String token; /** diff --git a/src/test/java/com/michelin/ns4kafka/integration/StreamTest.java b/src/test/java/com/michelin/ns4kafka/integration/StreamTest.java index 53dee92e..30af56ff 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/StreamTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/StreamTest.java @@ -16,10 +16,11 @@ import io.micronaut.http.HttpMethod; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.apache.kafka.clients.admin.Admin; import org.apache.kafka.common.acl.AccessControlEntryFilter; import org.apache.kafka.common.acl.AclBindingFilter; @@ -30,17 +31,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.util.List; import java.util.concurrent.ExecutionException; @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") -public class StreamTest extends AbstractIntegrationTest { - +class StreamTest extends AbstractIntegrationTest { @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; @Inject List aceAsyncExecutorList; @@ -48,7 +47,7 @@ public class StreamTest extends AbstractIntegrationTest { private String token; @BeforeAll - void init(){ + void init() { Namespace ns1 = Namespace.builder() .metadata(ObjectMeta.builder() .name("nskafkastream") diff --git a/src/test/java/com/michelin/ns4kafka/integration/TopicTest.java b/src/test/java/com/michelin/ns4kafka/integration/TopicTest.java index c7234bba..6e6bde29 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/TopicTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/TopicTest.java @@ -10,7 +10,6 @@ import com.michelin.ns4kafka.models.Namespace.NamespaceSpec; import com.michelin.ns4kafka.models.RoleBinding.*; import com.michelin.ns4kafka.models.Topic.TopicSpec; -import com.michelin.ns4kafka.services.TopicService; import com.michelin.ns4kafka.services.executors.TopicAsyncExecutor; import com.michelin.ns4kafka.validation.TopicValidator; import io.micronaut.context.annotation.Property; @@ -19,11 +18,12 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -35,7 +35,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.util.Collection; import java.util.List; import java.util.Map; @@ -48,28 +47,13 @@ @MicronautTest @Property(name = "micronaut.security.gitlab.enabled", value = "false") class TopicTest extends AbstractIntegrationTest { - /** - * The HTTP client - */ @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; - /** - * The topic sync executor - */ @Inject List topicAsyncExecutorList; - /** - * The topic service - */ - @Inject - TopicService topicService; - - /** - * The Authentication token - */ private String token; /** @@ -247,7 +231,7 @@ void createTopic() throws InterruptedException, ExecutionException { ConfigResource configResource = new ConfigResource(ConfigResource.Type.TOPIC,"ns1-topicFirstCreate"); List valueToVerify = kafkaClient.describeConfigs(List.of(configResource)).all().get().get(configResource).entries().stream() .filter(e -> configKey.contains(e.name())) - .collect(Collectors.toList()); + .toList(); Assertions.assertEquals(config.size(), valueToVerify.size()); valueToVerify.forEach(entry -> Assertions.assertEquals(config.get(entry.name()), entry.value())); @@ -316,7 +300,7 @@ void updateTopic() throws InterruptedException, ExecutionException { ConfigResource configResource = new ConfigResource(ConfigResource.Type.TOPIC,"ns1-topic2Create"); List valueToVerify = kafkaClient.describeConfigs(List.of(configResource)).all().get().get(configResource).entries().stream() .filter(e -> configKey.contains(e.name())) - .collect(Collectors.toList()); + .toList(); Assertions.assertEquals(config.size(), valueToVerify.size()); valueToVerify.forEach(entry -> { diff --git a/src/test/java/com/michelin/ns4kafka/integration/UserTest.java b/src/test/java/com/michelin/ns4kafka/integration/UserTest.java index 0124e17a..4ea5f624 100644 --- a/src/test/java/com/michelin/ns4kafka/integration/UserTest.java +++ b/src/test/java/com/michelin/ns4kafka/integration/UserTest.java @@ -11,11 +11,12 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.apache.kafka.clients.admin.ScramMechanism; import org.apache.kafka.clients.admin.UserScramCredentialsDescription; import org.apache.kafka.common.quota.ClientQuotaEntity; @@ -25,7 +26,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.inject.Inject; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -35,7 +35,7 @@ public class UserTest extends AbstractIntegrationTest { @Inject @Client("/") - RxHttpClient client; + Rx3HttpClient client; @Inject List userAsyncExecutors; @@ -43,7 +43,7 @@ public class UserTest extends AbstractIntegrationTest { private String token; @BeforeAll - void init() throws ExecutionException, InterruptedException { + void init() { Namespace ns1 = Namespace.builder() .metadata(ObjectMeta.builder() .name("ns1") @@ -98,7 +98,7 @@ void createAndUpdateUserForceTest() throws ExecutionException, InterruptedExcept } @Test - void updateUserFail_NotMatching() throws ExecutionException, InterruptedException { + void updateUserFail_NotMatching() { HttpClientResponseException exception = Assertions.assertThrows(HttpClientResponseException.class, () -> client.retrieve(HttpRequest.create(HttpMethod.POST, "/api/namespaces/ns1/users/user2/reset-password").bearerAuth(token), KafkaUserResetPassword.class).blockingFirst()); Assertions.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, exception.getStatus()); diff --git a/src/test/java/com/michelin/ns4kafka/models/AccessControlEntryTest.java b/src/test/java/com/michelin/ns4kafka/models/AccessControlEntryTest.java index 1f97908a..6f4c65d9 100644 --- a/src/test/java/com/michelin/ns4kafka/models/AccessControlEntryTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/AccessControlEntryTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class AccessControlEntryTest { +class AccessControlEntryTest { @Test void testEquals() { AccessControlEntry original = AccessControlEntry.builder() diff --git a/src/test/java/com/michelin/ns4kafka/models/ConnectValidatorTest.java b/src/test/java/com/michelin/ns4kafka/models/ConnectValidatorTest.java index 01e6504e..934bdf6f 100644 --- a/src/test/java/com/michelin/ns4kafka/models/ConnectValidatorTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/ConnectValidatorTest.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Map; -public class ConnectValidatorTest { +class ConnectValidatorTest { @Test void testEquals(){ ConnectValidator original = ConnectValidator.builder() diff --git a/src/test/java/com/michelin/ns4kafka/models/ConnectorTest.java b/src/test/java/com/michelin/ns4kafka/models/ConnectorTest.java index 5e916117..bd804ad9 100644 --- a/src/test/java/com/michelin/ns4kafka/models/ConnectorTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/ConnectorTest.java @@ -6,7 +6,7 @@ import java.util.Map; -public class ConnectorTest { +class ConnectorTest { @Test void testEquals() { Connector original = Connector.builder() diff --git a/src/test/java/com/michelin/ns4kafka/models/NamespaceTest.java b/src/test/java/com/michelin/ns4kafka/models/NamespaceTest.java index 1dbd770e..ef174a66 100644 --- a/src/test/java/com/michelin/ns4kafka/models/NamespaceTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/NamespaceTest.java @@ -7,7 +7,7 @@ import java.util.List; -public class NamespaceTest { +class NamespaceTest { @Test void testEquals() { Namespace original = Namespace.builder() diff --git a/src/test/java/com/michelin/ns4kafka/models/ObjectMetaTest.java b/src/test/java/com/michelin/ns4kafka/models/ObjectMetaTest.java index 6b4a5f99..a6c3985b 100644 --- a/src/test/java/com/michelin/ns4kafka/models/ObjectMetaTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/ObjectMetaTest.java @@ -7,7 +7,7 @@ import java.util.Date; import java.util.Map; -public class ObjectMetaTest { +class ObjectMetaTest { @Test void testEquals() { ObjectMeta original = ObjectMeta.builder() diff --git a/src/test/java/com/michelin/ns4kafka/models/ResourceValidatorTest.java b/src/test/java/com/michelin/ns4kafka/models/ResourceValidatorTest.java index 08a03f75..e9a81408 100644 --- a/src/test/java/com/michelin/ns4kafka/models/ResourceValidatorTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/ResourceValidatorTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class ResourceValidatorTest { +class ResourceValidatorTest { @Test void testNonEmptyString() { ResourceValidator.Validator original = new ResourceValidator.NonEmptyString(); diff --git a/src/test/java/com/michelin/ns4kafka/models/RoleBindingTest.java b/src/test/java/com/michelin/ns4kafka/models/RoleBindingTest.java index 0e5a17b5..40739870 100644 --- a/src/test/java/com/michelin/ns4kafka/models/RoleBindingTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/RoleBindingTest.java @@ -5,7 +5,7 @@ import java.util.List; -public class RoleBindingTest { +class RoleBindingTest { @Test void testEquals_Role() { RoleBinding.Role original = RoleBinding.Role.builder() diff --git a/src/test/java/com/michelin/ns4kafka/models/TopicValidatorTest.java b/src/test/java/com/michelin/ns4kafka/models/TopicValidatorTest.java index 6f449118..68f15835 100644 --- a/src/test/java/com/michelin/ns4kafka/models/TopicValidatorTest.java +++ b/src/test/java/com/michelin/ns4kafka/models/TopicValidatorTest.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Map; -public class TopicValidatorTest { +class TopicValidatorTest { @Test void testEquals() { TopicValidator original = TopicValidator.builder() diff --git a/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationProviderTest.java b/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationProviderTest.java index 538634c0..2f636797 100644 --- a/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationProviderTest.java +++ b/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationProviderTest.java @@ -9,9 +9,9 @@ import io.micronaut.http.HttpResponse; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.security.authentication.*; -import io.reactivex.Flowable; -import io.reactivex.Maybe; -import io.reactivex.subscribers.TestSubscriber; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.subscribers.TestSubscriber; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -21,38 +21,24 @@ import org.reactivestreams.Publisher; import java.util.List; +import java.util.concurrent.TimeUnit; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GitlabAuthenticationProviderTest { - /** - * The mocked GitLab authentication service - */ @Mock GitlabAuthenticationService gitlabAuthenticationService; - /** - * The mocked resource security service - */ @Mock ResourceBasedSecurityRule resourceBasedSecurityRule; - /** - * The role binding service - */ @Mock RoleBindingService roleBindingService; - /** - * The NS4Kafka security config service - */ @Mock SecurityConfig securityConfig; - /** - * The mocked Gitlab authentication provider - */ @InjectMocks GitlabAuthenticationProvider gitlabAuthenticationProvider; @@ -90,7 +76,7 @@ void authenticationSuccess() { TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = gitlabAuthenticationProvider.authenticate(null, authenticationRequest); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); @@ -98,13 +84,12 @@ void authenticationSuccess() { AuthenticationResponse actual = subscriber.values().get(0); Assertions.assertTrue(actual.isAuthenticated()); - Assertions.assertTrue(actual.getUserDetails().isPresent()); + Assertions.assertTrue(actual.getAuthentication().isPresent()); - UserDetails actualUserDetails = actual.getUserDetails().get(); - Assertions.assertEquals("email", actualUserDetails.getUsername()); - Assertions.assertIterableEquals(groups, (List)actualUserDetails.getAttributes("roles","username").get( "groups")); + Authentication actualUserDetails = actual.getAuthentication().get(); + Assertions.assertEquals("email", actualUserDetails.getName()); + Assertions.assertIterableEquals(groups, (List) actualUserDetails.getAttributes().get( "groups")); Assertions.assertIterableEquals(List.of(), actualUserDetails.getRoles(),"User has no custom roles"); - } /** @@ -130,7 +115,7 @@ void authenticationSuccessAdmin() { Publisher authenticationResponsePublisher = gitlabAuthenticationProvider.authenticate(null, authenticationRequest); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); @@ -138,11 +123,11 @@ void authenticationSuccessAdmin() { AuthenticationResponse actual = subscriber.values().get(0); Assertions.assertTrue(actual.isAuthenticated()); - Assertions.assertTrue(actual.getUserDetails().isPresent()); + Assertions.assertTrue(actual.getAuthentication().isPresent()); - UserDetails actualUserDetails = actual.getUserDetails().get(); - Assertions.assertEquals("email", actualUserDetails.getUsername()); - Assertions.assertIterableEquals(groups, (List)actualUserDetails.getAttributes("roles","username").get("groups")); + Authentication actualUserDetails = actual.getAuthentication().get(); + Assertions.assertEquals("email", actualUserDetails.getName()); + Assertions.assertIterableEquals(groups, (List) actualUserDetails.getAttributes().get("groups")); Assertions.assertIterableEquals(List.of(ResourceBasedSecurityRule.IS_ADMIN), actualUserDetails.getRoles(),"User has custom roles"); } @@ -160,7 +145,7 @@ void authenticationFailure() { Publisher authenticationResponsePublisher = gitlabAuthenticationProvider.authenticate(null, authenticationRequest); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(AuthenticationException.class); subscriber.assertValueCount(0); @@ -187,7 +172,7 @@ void authenticationFailureGroupsNotFound() { Publisher authenticationResponsePublisher = gitlabAuthenticationProvider.authenticate(null, authenticationRequest); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(AuthenticationException.class); subscriber.assertValueCount(0); diff --git a/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationServiceTest.java b/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationServiceTest.java index c47633ce..3c2bce5f 100644 --- a/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/security/GitlabAuthenticationServiceTest.java @@ -3,8 +3,9 @@ import com.michelin.ns4kafka.security.gitlab.GitlabApiClient; import com.michelin.ns4kafka.security.gitlab.GitlabAuthenticationService; import io.micronaut.http.HttpResponse; -import io.reactivex.Flowable; -import io.reactivex.subscribers.TestSubscriber; +import io.micronaut.http.MutableHttpResponse; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.subscribers.TestSubscriber; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,10 +17,10 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; @ExtendWith(MockitoExtension.class) -public class GitlabAuthenticationServiceTest { - +class GitlabAuthenticationServiceTest { @Mock GitlabApiClient gitlabApiClient; @@ -32,11 +33,11 @@ void findUserSuccess(){ Mockito.when(gitlabApiClient.findUser(token)) .thenReturn(Flowable.just(Map.of("user","test", "email", "user@mail.com"))); - TestSubscriber subscriber = new TestSubscriber(); + TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = gitlabAuthenticationService.findUsername(token).toFlowable(); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); @@ -49,19 +50,19 @@ void findUserSuccess(){ @Test void findGroupsOnePage(){ String token = "v4l1d_70k3n"; - HttpResponse pageOneResponse = HttpResponse + MutableHttpResponse>> pageOneResponse = HttpResponse .ok(List.of( - Map.of("full_path", "group1", "unusedKey", "unusedVal"), - Map.of("full_path", "group2", "unusedKey", "unusedVal")) - ) + Map.of("full_path", "group1", "unusedKey", "unusedVal"), + Map.of("full_path", "group2", "unusedKey", "unusedVal"))) .header("X-Total-Pages","1"); + Mockito.when(gitlabApiClient.getGroupsPage(token,1)).thenReturn(Flowable.just(pageOneResponse)); - TestSubscriber subscriber = new TestSubscriber(); + TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = gitlabAuthenticationService.findAllGroups(token); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); @@ -70,38 +71,39 @@ void findGroupsOnePage(){ List actual = subscriber.values(); Assertions.assertIterableEquals(List.of("group1","group2"), actual); } + @Test void findGroupsThreePages(){ String token = "v4l1d_70k3n"; - HttpResponse pageOneResponse = HttpResponse + MutableHttpResponse>> pageOneResponse = HttpResponse .ok(List.of( - Map.of("full_path", "group1", "unusedKey", "unusedVal"), - Map.of("full_path", "group2", "unusedKey", "unusedVal")) - ) + Map.of("full_path", "group1", "unusedKey", "unusedVal"), + Map.of("full_path", "group2", "unusedKey", "unusedVal"))) .header("X-Next-Page","2") .header("X-Total-Pages","3"); - HttpResponse pageTwoResponse = HttpResponse + + MutableHttpResponse>> pageTwoResponse = HttpResponse .ok(List.of( - Map.of("full_path", "group3", "unusedKey", "unusedVal"), - Map.of("full_path", "group4", "unusedKey", "unusedVal")) - ) + Map.of("full_path", "group3", "unusedKey", "unusedVal"), + Map.of("full_path", "group4", "unusedKey", "unusedVal"))) .header("X-Next-Page","3") .header("X-Total-Pages","3"); - HttpResponse pageThreeResponse = HttpResponse + + MutableHttpResponse>> pageThreeResponse = HttpResponse .ok(List.of( - Map.of("full_path", "group5", "unusedKey", "unusedVal"), - Map.of("full_path", "group6", "unusedKey", "unusedVal")) - ) + Map.of("full_path", "group5", "unusedKey", "unusedVal"), + Map.of("full_path", "group6", "unusedKey", "unusedVal"))) .header("X-Total-Pages","3"); + Mockito.when(gitlabApiClient.getGroupsPage(token,1)).thenReturn(Flowable.just(pageOneResponse)); Mockito.when(gitlabApiClient.getGroupsPage(token,2)).thenReturn(Flowable.just(pageTwoResponse)); Mockito.when(gitlabApiClient.getGroupsPage(token,3)).thenReturn(Flowable.just(pageThreeResponse)); - TestSubscriber subscriber = new TestSubscriber(); + TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = gitlabAuthenticationService.findAllGroups(token); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); diff --git a/src/test/java/com/michelin/ns4kafka/security/LocalUserAuthenticationProviderTest.java b/src/test/java/com/michelin/ns4kafka/security/LocalUserAuthenticationProviderTest.java index 27094c5f..a59b129a 100644 --- a/src/test/java/com/michelin/ns4kafka/security/LocalUserAuthenticationProviderTest.java +++ b/src/test/java/com/michelin/ns4kafka/security/LocalUserAuthenticationProviderTest.java @@ -3,10 +3,10 @@ import com.michelin.ns4kafka.config.SecurityConfig; import com.michelin.ns4kafka.security.local.LocalUser; import com.michelin.ns4kafka.security.local.LocalUserAuthenticationProvider; +import io.micronaut.security.authentication.Authentication; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.UserDetails; import io.micronaut.security.authentication.UsernamePasswordCredentials; -import io.reactivex.subscribers.TestSubscriber; +import io.reactivex.rxjava3.subscribers.TestSubscriber; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,13 +18,16 @@ import org.reactivestreams.Publisher; import java.util.List; +import java.util.concurrent.TimeUnit; @ExtendWith(MockitoExtension.class) -public class LocalUserAuthenticationProviderTest { +class LocalUserAuthenticationProviderTest { @Mock SecurityConfig securityConfig; + @Mock ResourceBasedSecurityRule resourceBasedSecurityRule; + @InjectMocks LocalUserAuthenticationProvider localUserAuthenticationProvider; @@ -35,18 +38,17 @@ void authenticateNoMatchUser() { Mockito.when(securityConfig.getLocalUsers()) .thenReturn(List.of()); - TestSubscriber subscriber = new TestSubscriber(); + TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = localUserAuthenticationProvider.authenticate(null, credentials); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); - - //then + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); subscriber.assertValueCount(0); } + @Test void authenticateMatchUserNoMatchPassword() { UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "admin"); @@ -57,18 +59,17 @@ void authenticateMatchUserNoMatchPassword() { .password("invalid_sha256_signature") .build())); - TestSubscriber subscriber = new TestSubscriber(); + TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = localUserAuthenticationProvider.authenticate(null, credentials); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); - - //then + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); subscriber.assertValueCount(0); } + @Test void authenticateMatchUserMatchPassword() { UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "admin"); @@ -82,13 +83,11 @@ void authenticateMatchUserMatchPassword() { Mockito.when(resourceBasedSecurityRule.computeRolesFromGroups(ArgumentMatchers.any())) .thenReturn(List.of()); - TestSubscriber subscriber = new TestSubscriber(); + TestSubscriber subscriber = new TestSubscriber<>(); Publisher authenticationResponsePublisher = localUserAuthenticationProvider.authenticate(null, credentials); authenticationResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); - - //then + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertComplete(); subscriber.assertNoErrors(); @@ -96,9 +95,9 @@ void authenticateMatchUserMatchPassword() { AuthenticationResponse actual = subscriber.values().get(0); Assertions.assertTrue(actual.isAuthenticated()); - Assertions.assertTrue(actual.getUserDetails().isPresent()); + Assertions.assertTrue(actual.getAuthentication().isPresent()); - UserDetails actualUserDetails = actual.getUserDetails().get(); - Assertions.assertEquals("admin", actualUserDetails.getUsername()); + Authentication actualUserDetails = actual.getAuthentication().get(); + Assertions.assertEquals("admin", actualUserDetails.getName()); } } diff --git a/src/test/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRuleTest.java b/src/test/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRuleTest.java index 01286b1a..3a1642b4 100644 --- a/src/test/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRuleTest.java +++ b/src/test/java/com/michelin/ns4kafka/security/ResourceBasedSecurityRuleTest.java @@ -7,6 +7,7 @@ import com.michelin.ns4kafka.repositories.NamespaceRepository; import com.michelin.ns4kafka.repositories.RoleBindingRepository; import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.Authentication; import io.micronaut.security.rules.SecurityRuleResult; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -35,77 +36,92 @@ class ResourceBasedSecurityRuleTest { ResourceBasedSecurityRule resourceBasedSecurityRule; @Test - void CheckReturnsUnknownUnauthenticated(){ - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/anything"),null,null); + void checkReturnsUnknownUnauthenticated(){ + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/anything"),null); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @Test - void CheckReturnsUnknownMissingClaims(){ + void checkReturnsUnknownMissingClaims(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/anything"),null,claims); + Authentication auth = Authentication.build("user", claims); + + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/anything"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @Test - void CheckReturnsUnknownInvalidResource(){ + void checkReturnsUnknownInvalidResource(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/non-namespaced/resource"),null, claims); + Authentication auth = Authentication.build("user", claims); + + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/non-namespaced/resource"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @Test - void CheckReturnsUnknownNoRoleBinding(){ + void checkReturnsUnknownNoRoleBinding(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); Mockito.when(roleBindingRepository.findAllForGroups(groups)) .thenReturn(List.of()); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @Test - void CheckReturnsUnknownInvalidNamespace(){ + void checkReturnsUnknownInvalidNamespace(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.empty()); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } + @Test - void CheckReturnsUnknownAdminNamespaceAsNotAdmin(){ + void checkReturnsUnknownAdminNamespaceAsNotAdmin(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/admin/connectors"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/admin/connectors"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @Test - void CheckReturnsUnknownInvalidNamespaceAsAdmin(){ + void checkReturnsUnknownInvalidNamespaceAsAdmin(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of("isAdmin()")); + Authentication auth = Authentication.build("user", List.of("isAdmin()"), claims); + Mockito.when(namespaceRepository.findByName("admin")) .thenReturn(Optional.empty()); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/admin/connectors"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/admin/connectors"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } + @Test - void CheckReturnsAllowedNamespaceAsAdmin(){ + void checkReturnsAllowedNamespaceAsAdmin(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of("isAdmin()")); + Authentication auth = Authentication.build("user", List.of("isAdmin()"), claims); + Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors"), auth); Assertions.assertEquals(SecurityRuleResult.ALLOWED, actual); } @@ -113,6 +129,8 @@ void CheckReturnsAllowedNamespaceAsAdmin(){ void checkReturnsAllowed(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(roleBindingRepository.findAllForGroups(groups)) .thenReturn(List.of(RoleBinding.builder() .metadata(ObjectMeta.builder().namespace("test") @@ -129,7 +147,7 @@ void checkReturnsAllowed(){ Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors"), auth); Assertions.assertEquals(SecurityRuleResult.ALLOWED, actual); } @@ -137,6 +155,8 @@ void checkReturnsAllowed(){ void CheckReturnsAllowedSubresource() { List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(roleBindingRepository.findAllForGroups(groups)) .thenReturn(List.of(RoleBinding.builder() .metadata(ObjectMeta.builder().namespace("test") @@ -153,10 +173,10 @@ void CheckReturnsAllowedSubresource() { Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors/name/restart"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors/name/restart"), auth); Assertions.assertEquals(SecurityRuleResult.ALLOWED, actual); - actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/topics/name/delete-records"),null, claims); + actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/topics/name/delete-records"), auth); Assertions.assertEquals(SecurityRuleResult.ALLOWED, actual); } @@ -164,6 +184,8 @@ void CheckReturnsAllowedSubresource() { void CheckReturnsAllowedResourceWithHyphen() { List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(roleBindingRepository.findAllForGroups(groups)) .thenReturn(List.of(RoleBinding.builder() .metadata(ObjectMeta.builder().namespace("test") @@ -180,7 +202,7 @@ void CheckReturnsAllowedResourceWithHyphen() { Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/role-bindings"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/role-bindings"), auth); Assertions.assertEquals(SecurityRuleResult.ALLOWED, actual); } @@ -188,6 +210,8 @@ void CheckReturnsAllowedResourceWithHyphen() { void CheckReturnsAllowedResourceNameWithDot() { List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(roleBindingRepository.findAllForGroups(groups)) .thenReturn(List.of(RoleBinding.builder() .metadata(ObjectMeta.builder().namespace("test") @@ -204,7 +228,7 @@ void CheckReturnsAllowedResourceNameWithDot() { Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/topics/topic.with.dots"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/topics/topic.with.dots"), auth); Assertions.assertEquals(SecurityRuleResult.ALLOWED, actual); } @@ -212,9 +236,11 @@ void CheckReturnsAllowedResourceNameWithDot() { void CheckReturnsUnknownSubResource(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); - Mockito.when(roleBindingRepository.findAllForGroups(groups)) + Mockito.when(roleBindingRepository.findAllForGroups(groups)) .thenReturn(List.of(RoleBinding.builder() .metadata(ObjectMeta.builder().namespace("test") .build()) @@ -228,7 +254,7 @@ void CheckReturnsUnknownSubResource(){ .build()) .build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors/name/restart"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors/name/restart"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @@ -236,6 +262,8 @@ void CheckReturnsUnknownSubResource(){ void CheckReturnsUnknownSubResourceWithDot(){ List groups = List.of("group1"); Map claims = Map.of("sub","user", "groups", groups, "roles", List.of()); + Authentication auth = Authentication.build("user", claims); + Mockito.when(namespaceRepository.findByName("test")) .thenReturn(Optional.of(Namespace.builder().build())); Mockito.when(roleBindingRepository.findAllForGroups(groups)) @@ -252,12 +280,12 @@ void CheckReturnsUnknownSubResourceWithDot(){ .build()) .build())); - SecurityRuleResult actual = resourceBasedSecurityRule.check(HttpRequest.GET("/api/namespaces/test/connectors/name.with.dots/restart"),null, claims); + SecurityRuleResult actual = resourceBasedSecurityRule.checkSecurity(HttpRequest.GET("/api/namespaces/test/connectors/name.with.dots/restart"), auth); Assertions.assertEquals(SecurityRuleResult.UNKNOWN, actual); } @Test - void ComputeRolesNoAdmin(){ + void ComputeRolesNoAdmin() { Mockito.when(securityConfig.getAdminGroup()) .thenReturn("admin-group"); List actual = resourceBasedSecurityRule.computeRolesFromGroups(List.of("not-admin")); @@ -266,7 +294,7 @@ void ComputeRolesNoAdmin(){ } @Test - void ComputeRolesAdmin(){ + void ComputeRolesAdmin() { Mockito.when(securityConfig.getAdminGroup()) .thenReturn("admin-group"); List actual = resourceBasedSecurityRule.computeRolesFromGroups(List.of("admin-group")); diff --git a/src/test/java/com/michelin/ns4kafka/services/ConnectClusterServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/ConnectClusterServiceTest.java index 7215349c..e0942b83 100644 --- a/src/test/java/com/michelin/ns4kafka/services/ConnectClusterServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/ConnectClusterServiceTest.java @@ -10,10 +10,10 @@ import com.nimbusds.jose.JOSEException; import io.micronaut.http.HttpResponse; import io.micronaut.http.MutableHttpRequest; -import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientException; -import io.reactivex.Flowable; +import io.micronaut.rxjava3.http.client.Rx3HttpClient; +import io.reactivex.rxjava3.core.Flowable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -50,7 +50,7 @@ class ConnectClusterServiceTest { @Mock @Client("/") - RxHttpClient httpClient; + Rx3HttpClient httpClient; /** * Test find all @@ -328,7 +328,8 @@ void validateConnectClusterCreationAlreadyDefined() { KafkaAsyncExecutorConfig kafka = new KafkaAsyncExecutorConfig("local"); kafka.setConnects(Map.of("test-connect", new KafkaAsyncExecutorConfig.ConnectConfig())); when(kafkaAsyncExecutorConfigList.stream()).thenReturn(Stream.of(kafka)); - when(httpClient.exchange(any(MutableHttpRequest.class))).thenReturn(Flowable.just(HttpResponse.ok())); + when(httpClient.exchange(any(MutableHttpRequest.class))) + .thenReturn(Flowable.just(HttpResponse.ok())); List errors = connectClusterService.validateConnectClusterCreation(connectCluster); diff --git a/src/test/java/com/michelin/ns4kafka/services/ConnectorClientProxyTest.java b/src/test/java/com/michelin/ns4kafka/services/ConnectorClientProxyTest.java index b4684f3a..c416f14b 100644 --- a/src/test/java/com/michelin/ns4kafka/services/ConnectorClientProxyTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/ConnectorClientProxyTest.java @@ -12,7 +12,7 @@ import io.micronaut.http.*; import io.micronaut.http.client.ProxyHttpClient; import io.micronaut.http.simple.SimpleHttpRequest; -import io.reactivex.subscribers.TestSubscriber; +import io.reactivex.rxjava3.subscribers.TestSubscriber; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @ExtendWith(MockitoExtension.class) @@ -51,10 +52,10 @@ void doFilterMissingHeaderSecret() { .header("X-Unused", "123"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(ResourceValidationException.class); subscriber.assertError(throwable -> @@ -71,10 +72,10 @@ void doFilterWrongSecret() { .header("X-Proxy-Secret", "123"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(ResourceValidationException.class); subscriber.assertError(throwable -> @@ -91,10 +92,10 @@ void doFilterMissingHeaderKafkaCluster() { .header("X-Unused", "123"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(ResourceValidationException.class); subscriber.assertError(throwable -> @@ -111,10 +112,10 @@ void doFilterMissingHeaderConnectCluster() { .header(ConnectorClientProxy.PROXY_HEADER_KAFKA_CLUSTER, "local"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(ResourceValidationException.class); subscriber.assertError(throwable -> @@ -134,10 +135,10 @@ void doFilterWrongKafkaCluster() { Mockito.when(kafkaAsyncExecutorConfigs.stream()).thenReturn(Stream.empty()); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(throwable -> ((ResourceValidationException)throwable) @@ -160,10 +161,10 @@ void doFilterWrongConnectCluster() { .thenReturn(Stream.of(config)); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertError(throwable -> ((ResourceValidationException)throwable) @@ -192,10 +193,10 @@ void doFilterSuccess() { .thenReturn(Publishers.just(HttpResponse.ok())); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertValueCount(1); subscriber.assertValue(mutableHttpResponse -> mutableHttpResponse.status() == HttpStatus.OK); @@ -234,10 +235,10 @@ void doFilterSuccessSelfDeployedConnectCluster() { .thenReturn("changeitchangeitchangeitchangeit"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); - subscriber.awaitTerminalEvent(); + subscriber.awaitDone(1L, TimeUnit.SECONDS); subscriber.assertValueCount(1); subscriber.assertValue(mutableHttpResponse -> mutableHttpResponse.status() == HttpStatus.OK); diff --git a/src/test/java/com/michelin/ns4kafka/services/ConnectorServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/ConnectorServiceTest.java index 72a297f1..caad6ac1 100644 --- a/src/test/java/com/michelin/ns4kafka/services/ConnectorServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/ConnectorServiceTest.java @@ -1,8 +1,11 @@ package com.michelin.ns4kafka.services; -import com.michelin.ns4kafka.models.*; -import com.michelin.ns4kafka.models.connector.Connector; +import com.michelin.ns4kafka.models.AccessControlEntry; +import com.michelin.ns4kafka.models.ConnectCluster; +import com.michelin.ns4kafka.models.Namespace; import com.michelin.ns4kafka.models.Namespace.NamespaceSpec; +import com.michelin.ns4kafka.models.ObjectMeta; +import com.michelin.ns4kafka.models.connector.Connector; import com.michelin.ns4kafka.repositories.ConnectorRepository; import com.michelin.ns4kafka.services.connect.ConnectorClientProxy; import com.michelin.ns4kafka.services.connect.client.ConnectorClient; @@ -15,8 +18,8 @@ import io.micronaut.http.HttpStatus; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.inject.qualifiers.Qualifiers; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -845,8 +848,6 @@ void listUnsynchronizedPartialExistingConnectors() { .build() )); - // partial number of topics exists into ns4kfk - // all connects exists into ns4kfk Mockito.when(connectorRepository.findAllForCluster("local")) .thenReturn(List.of(c1)); diff --git a/src/test/java/com/michelin/ns4kafka/services/ConsumerGroupServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/ConsumerGroupServiceTest.java index 49b86438..dc9b5d02 100644 --- a/src/test/java/com/michelin/ns4kafka/services/ConsumerGroupServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/ConsumerGroupServiceTest.java @@ -28,15 +28,9 @@ @ExtendWith(MockitoExtension.class) class ConsumerGroupServiceTest { - /** - * The mocked application context - */ @Mock ApplicationContext applicationContext; - /** - * The mocked consumer group service - */ @InjectMocks ConsumerGroupService consumerGroupService; diff --git a/src/test/java/com/michelin/ns4kafka/services/KafkaSchemaRegistryClientProxyTest.java b/src/test/java/com/michelin/ns4kafka/services/KafkaSchemaRegistryClientProxyTest.java index 68cb0dcb..9d5b9c3c 100644 --- a/src/test/java/com/michelin/ns4kafka/services/KafkaSchemaRegistryClientProxyTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/KafkaSchemaRegistryClientProxyTest.java @@ -1,12 +1,12 @@ package com.michelin.ns4kafka.services; -import com.michelin.ns4kafka.utils.exceptions.ResourceValidationException; import com.michelin.ns4kafka.config.KafkaAsyncExecutorConfig; import com.michelin.ns4kafka.services.schema.KafkaSchemaRegistryClientProxy; +import com.michelin.ns4kafka.utils.exceptions.ResourceValidationException; import io.micronaut.http.*; import io.micronaut.http.client.ProxyHttpClient; import io.micronaut.http.simple.SimpleHttpRequest; -import io.reactivex.subscribers.TestSubscriber; +import io.reactivex.rxjava3.subscribers.TestSubscriber; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,21 +25,12 @@ @ExtendWith(MockitoExtension.class) class KafkaSchemaRegistryClientProxyTest { - /** - * HTTP client - */ @Mock ProxyHttpClient client; - /** - * Managed clusters configuration - */ @Mock List kafkaAsyncExecutorConfigs; - /** - * Kafka schema registry client proxy - */ @InjectMocks KafkaSchemaRegistryClientProxy proxy; @@ -52,7 +43,7 @@ void doFilterMissingSecretHeader() { .GET("http://localhost/schema-registry-proxy/noHeader"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); subscriber.awaitDone(1L, TimeUnit.SECONDS); @@ -71,7 +62,7 @@ void doFilterWrongSecret() { .header(KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET, "123"); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); subscriber.awaitDone(1L, TimeUnit.SECONDS); @@ -90,7 +81,7 @@ void doFilterMissingKafkaClusterHeader() { .header(KafkaSchemaRegistryClientProxy.PROXY_HEADER_SECRET, KafkaSchemaRegistryClientProxy.PROXY_SECRET); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); subscriber.awaitDone(1L, TimeUnit.SECONDS); @@ -112,7 +103,7 @@ void doFilterNoKafkaClusterFoundInConfig() { when(kafkaAsyncExecutorConfigs.stream()).thenReturn(Stream.empty()); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); subscriber.awaitDone(1L, TimeUnit.SECONDS); @@ -133,7 +124,7 @@ void doFilterNoSchemaRegistryFoundInConfig() { when(kafkaAsyncExecutorConfigs.stream()).thenReturn(Stream.of(new KafkaAsyncExecutorConfig("local"))); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); subscriber.awaitDone(1L, TimeUnit.SECONDS); @@ -162,7 +153,7 @@ void doFilterSuccess() { .thenReturn(just(HttpResponse.ok())); TestSubscriber> subscriber = new TestSubscriber<>(); - Publisher> mutableHttpResponsePublisher = proxy.doFilterOnce(request, null); + Publisher> mutableHttpResponsePublisher = proxy.doFilter(request, null); mutableHttpResponsePublisher.subscribe(subscriber); subscriber.awaitDone(1L, TimeUnit.SECONDS); @@ -219,7 +210,7 @@ void mutateSchemaRegistryRequestWithBasicAuth() { * Implementation of a mutable HTTP request for tests * @param */ - public class MutableSimpleHttpRequest extends SimpleHttpRequest { + public static class MutableSimpleHttpRequest extends SimpleHttpRequest { @Override public MutableHttpRequest mutate() { diff --git a/src/test/java/com/michelin/ns4kafka/services/NamespaceServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/NamespaceServiceTest.java index 4b195e24..a3bbfb65 100644 --- a/src/test/java/com/michelin/ns4kafka/services/NamespaceServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/NamespaceServiceTest.java @@ -19,18 +19,22 @@ import java.util.stream.Stream; @ExtendWith(MockitoExtension.class) -public class NamespaceServiceTest { - +class NamespaceServiceTest { @Mock NamespaceRepository namespaceRepository; + @Mock TopicService topicService; + @Mock RoleBindingService roleBindingService; + @Mock AccessControlEntryService accessControlEntryService; + @Mock ConnectorService connectorService; + @Mock List kafkaAsyncExecutorConfigList; @@ -39,7 +43,6 @@ public class NamespaceServiceTest { @Test void validationCreationNoClusterFail() { - Namespace ns = Namespace.builder() .metadata(ObjectMeta.builder() .name("namespace") @@ -196,17 +199,6 @@ void validateFail() { .build()) .build(); - Namespace ns2 = Namespace.builder() - .metadata(ObjectMeta.builder() - .name("namespace2") - .cluster("local") - .build()) - .spec(NamespaceSpec.builder() - .connectClusters(List.of("local-name")) - .kafkaUser("user2") - .build()) - .build(); - KafkaAsyncExecutorConfig kafkaAsyncExecutorConfig1 = new KafkaAsyncExecutorConfig("local"); kafkaAsyncExecutorConfig1.setConnects(Map.of("other-connect-config", new ConnectConfig())); diff --git a/src/test/java/com/michelin/ns4kafka/services/RoleBindingServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/RoleBindingServiceTest.java index 7decf268..1d8f5fc5 100644 --- a/src/test/java/com/michelin/ns4kafka/services/RoleBindingServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/RoleBindingServiceTest.java @@ -1,25 +1,21 @@ package com.michelin.ns4kafka.services; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -import java.util.List; - -import com.michelin.ns4kafka.models.Namespace; -import com.michelin.ns4kafka.models.Namespace.NamespaceSpec; import com.michelin.ns4kafka.models.ObjectMeta; import com.michelin.ns4kafka.models.RoleBinding; import com.michelin.ns4kafka.repositories.RoleBindingRepository; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) -public class RoleBindingServiceTest { +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) +class RoleBindingServiceTest { @Mock RoleBindingRepository roleBindingRepository; @@ -28,18 +24,6 @@ public class RoleBindingServiceTest { @Test void findByName() { - - // init ns4kfk namespace - Namespace ns = Namespace.builder() - .metadata(ObjectMeta.builder() - .name("namespace") - .cluster("local") - .build()) - .spec(NamespaceSpec.builder() - .connectClusters(List.of("local-name")) - .build()) - .build(); - RoleBinding rb1 = RoleBinding.builder() .metadata(ObjectMeta.builder() .name("namespace-rb1") @@ -63,8 +47,5 @@ void findByName() { var result = roleBindingService.findByName("namespace", "namespace-rb2"); assertEquals(rb2, result.get()); - } - - } diff --git a/src/test/java/com/michelin/ns4kafka/services/SchemaServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/SchemaServiceTest.java index a1b09264..a0aa5860 100644 --- a/src/test/java/com/michelin/ns4kafka/services/SchemaServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/SchemaServiceTest.java @@ -9,8 +9,8 @@ import com.michelin.ns4kafka.services.schema.client.entities.SchemaCompatibilityCheckResponse; import com.michelin.ns4kafka.services.schema.client.entities.SchemaCompatibilityResponse; import com.michelin.ns4kafka.services.schema.client.entities.SchemaResponse; -import io.reactivex.Maybe; -import io.reactivex.Single; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Single; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,21 +27,12 @@ @ExtendWith(MockitoExtension.class) class SchemaServiceTest { - /** - * The schema service - */ @InjectMocks SchemaService schemaService; - /** - * The schema service - */ @Mock AccessControlEntryService accessControlEntryService; - /** - * Kafka schema registry client - */ @Mock KafkaSchemaRegistryClient kafkaSchemaRegistryClient; diff --git a/src/test/java/com/michelin/ns4kafka/services/StreamServiceTest.java b/src/test/java/com/michelin/ns4kafka/services/StreamServiceTest.java index 06eaed4b..aa9994d5 100644 --- a/src/test/java/com/michelin/ns4kafka/services/StreamServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/services/StreamServiceTest.java @@ -17,8 +17,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -public class StreamServiceTest { - +class StreamServiceTest { @InjectMocks StreamService streamService; diff --git a/src/test/java/com/michelin/ns4kafka/testcontainers/KafkaConnectContainer.java b/src/test/java/com/michelin/ns4kafka/testcontainers/KafkaConnectContainer.java index 05a126cc..b7ff83e6 100644 --- a/src/test/java/com/michelin/ns4kafka/testcontainers/KafkaConnectContainer.java +++ b/src/test/java/com/michelin/ns4kafka/testcontainers/KafkaConnectContainer.java @@ -1,17 +1,10 @@ package com.michelin.ns4kafka.testcontainers; -import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; -import org.testcontainers.utility.MountableFile; -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; import java.time.Duration; -import java.util.Objects; -import java.util.Set; import java.util.UUID; import static java.lang.String.format; @@ -22,13 +15,8 @@ * @see KafkaConnectContainer.java */ public class KafkaConnectContainer extends GenericContainer { - public static final int CONNECT_REST_PORT_INTERNAL = 8083; - /** - * Key / value for Configuration - */ public static final String GROUP_ID_CONFIG = "CONNECT_GROUP_ID"; - public static final String OFFSET_STORAGE_FILE_FILENAME_CONFIG = "CONNECT_OFFSET_STORAGE_FILE_FILENAME"; public static final String OFFSET_STORAGE_TOPIC_CONFIG = "CONNECT_OFFSET_STORAGE_TOPIC"; public static final String OFFSET_STORAGE_PARTITIONS_CONFIG = "CONNECT_OFFSET_STORAGE_PARTITIONS"; public static final String CONFIG_STORAGE_TOPIC_CONFIG = "CONNECT_CONFIG_STORAGE_TOPIC"; @@ -38,10 +26,8 @@ public class KafkaConnectContainer extends GenericContainer createContainerCmd.withName("testcontainsers-kafka-connect-" + UUID.randomUUID())); - - - if (hasKeyAvroConverter) { - Objects.requireNonNull(this.schemaRegistryUrl, "Schema registry URL not defined !!"); - this.withEnv(KEY_CONVERTER_SCHEMA_REGISTRY_URL_CONFIG, this.schemaRegistryUrl); - this.withEnv(KEY_CONVERTER_SCHEMAS_ENABLE_CONFIG, "true"); - } - if (hasValueAvroConverter) { - Objects.requireNonNull(this.schemaRegistryUrl, "Schema registry URL not defined !!"); - this.withEnv(VALUE_CONVERTER_SCHEMA_REGISTRY_URL_CONFIG, this.schemaRegistryUrl); - this.withEnv(VALUE_CONVERTER_SCHEMAS_ENABLE_CONFIG, "true"); - } - } - - public KafkaConnectContainer withSchemaRegistryUrl(String schemaRegistryUrl) { - this.schemaRegistryUrl = schemaRegistryUrl; - return this; } /** - * Set the group id - * - * @param groupId - * @return - */ - public KafkaConnectContainer withGroupId(String groupId) { - if (groupId != null) { - withEnv(GROUP_ID_CONFIG, groupId); - } - return this; - } - - /** - * Set the config storage topic. - * - * @param topic - * @return - */ - public KafkaConnectContainer withConfigStorageTopic(String topic) { - if (topic != null) { - withEnv(CONFIG_STORAGE_TOPIC_CONFIG, topic); - } - return this; - } - - /** - * Set the topic name of the storage offsets topic. - * - * @param topic - * @return - */ - public KafkaConnectContainer withOffsetStorageTopic(String topic) { - if (topic != null) { - withEnv(OFFSET_STORAGE_TOPIC_CONFIG, topic); - } - return this; - } - - /** - * Set the offsets storage partition. - * - * @param partitions - * @return - */ - public KafkaConnectContainer withOffsetStoragePartition(Integer partitions) { - if (partitions != null) { - withEnv(OFFSET_STORAGE_PARTITIONS_CONFIG, String.valueOf(partitions)); - } - return this; - } - - /** - * Set the status storage topic's name. - * - * @param topic - * @return - */ - public KafkaConnectContainer withStatusStorageTopic(String topic) { - if (topic != null) { - withEnv(STATUS_STORAGE_TOPIC_CONFIG, topic); - } - return this; - } - - /** - * Set the status storage partition. - * - * @param partitions - * @return - */ - public KafkaConnectContainer withStatusStoragePartition(Integer partitions) { - if (partitions != null) { - withEnv(STATUS_STORAGE_PARTITIONS_CONFIG, String.valueOf(partitions)); - } - return this; - } - - /** - * Set the offsets storage file name. - * - * @param storageFilename - * @return - */ - public KafkaConnectContainer withOffsetStorageFilename(String storageFilename) { - if (storageFilename != null) { - withEnv(OFFSET_STORAGE_FILE_FILENAME_CONFIG, storageFilename); - } - return this; - } - - /** - * Set the key converter. - * - * @param keyConverter - * @return - */ - public KafkaConnectContainer withKeyConverter(String keyConverter) { - if (keyConverter != null) { - withEnv(KEY_CONVERTER_CONFIG, keyConverter); - this.hasKeyAvroConverter = keyConverter.contains(AVRO_CONVERTER_PATTERN); - } - return this; - } - - /** - * Set the value converter. - * - * @param valueConverter - * @return - */ - public KafkaConnectContainer withValueConverter(String valueConverter) { - if (valueConverter != null) { - withEnv(VALUE_CONVERTER_CONFIG, valueConverter); - this.hasValueAvroConverter = valueConverter.contains(AVRO_CONVERTER_PATTERN); - } - return this; - } - - /** - * Set the list of plugins directory. - * - * @param plugins - * @return - */ - public KafkaConnectContainer withPlugins(Set plugins) { - if (plugins == null) { - return this; - } - plugins.forEach(this::withPlugins); - return this; - } - - /** - * Set the plugins directory. - * - * @param plugins - * @return - */ - public KafkaConnectContainer withPlugins(String plugins) { - if (plugins == null) { - return this; - } - MountableFile mountableFile = MountableFile.forClasspathResource(plugins); - Path pluginsPath = Paths.get(mountableFile.getResolvedPath()); - File pluginsFile = pluginsPath.toFile(); - if (!pluginsFile.exists()) { - throw new IllegalArgumentException(format("Resource with path %s could not be found", pluginsPath.toString())); - } - String containerPath = PLUGIN_PATH_CONTAINER; - if (pluginsFile.isDirectory()) { - containerPath += "/" + pluginsPath.getFileName(); - } else { - containerPath += "/" + pluginsPath.getParent().getFileName() + "/" + pluginsPath.getFileName(); - } - // Create the volume that will be need for scripts - this.addFileSystemBind(mountableFile.getResolvedPath(), containerPath, BindMode.READ_ONLY); - return this; - } - - /** - * Get the url. - * - * @return + * Get the url of Kafka Connect + * @return The URL */ public String getUrl() { - return format("http://%s:%d", this.getContainerIpAddress(), getMappedPort(CONNECT_REST_PORT_INTERNAL)); - } - - /** - * Get the local url - * - * @return - */ - public String getInternalUrl() { - return format("http://%s:%d", this.getNetworkAliases().get(0), CONNECT_REST_PORT_INTERNAL); + return format("http://%s:%d", this.getHost(), getMappedPort(CONNECT_REST_PORT_INTERNAL)); } } diff --git a/src/test/java/com/michelin/ns4kafka/testcontainers/SchemaRegistryContainer.java b/src/test/java/com/michelin/ns4kafka/testcontainers/SchemaRegistryContainer.java index 377ae607..8429e259 100644 --- a/src/test/java/com/michelin/ns4kafka/testcontainers/SchemaRegistryContainer.java +++ b/src/test/java/com/michelin/ns4kafka/testcontainers/SchemaRegistryContainer.java @@ -13,16 +13,8 @@ * Docker container. */ public class SchemaRegistryContainer extends GenericContainer { - /** - * Schema Registry port - */ private static final int SCHEMA_REGISTRY_INTERNAL_PORT = 8081; - /** - * Schema Registry network alias - */ - private final String networkAlias = "schema-registry"; - /** * Constructor * @@ -37,27 +29,16 @@ public SchemaRegistryContainer(DockerImageName dockerImageName, String bootstrap .withEnv("SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS", bootstrapServers); withExposedPorts(SCHEMA_REGISTRY_INTERNAL_PORT); - withNetworkAliases(networkAlias); - + withNetworkAliases("schema-registry"); waitingFor(Wait.forHttp("/subjects")); } /** * Get the current URL of the schema registry - * * @return The current URL of the schema registry */ public String getUrl() { - return format("http://%s:%d", this.getContainerIpAddress(), this.getMappedPort(SCHEMA_REGISTRY_INTERNAL_PORT)); - } - - /** - * Get the local url - * - * @return - */ - public String getInternalUrl() { - return format("http://%s:%d", networkAlias, SCHEMA_REGISTRY_INTERNAL_PORT); + return format("http://%s:%d", this.getHost(), this.getMappedPort(SCHEMA_REGISTRY_INTERNAL_PORT)); } } diff --git a/src/test/java/com/michelin/ns4kafka/utils/EncryptionUtilsTest.java b/src/test/java/com/michelin/ns4kafka/utils/EncryptionUtilsTest.java index c402052f..bdb4105f 100644 --- a/src/test/java/com/michelin/ns4kafka/utils/EncryptionUtilsTest.java +++ b/src/test/java/com/michelin/ns4kafka/utils/EncryptionUtilsTest.java @@ -3,8 +3,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.apache.commons.lang3.StringUtils.EMPTY; - class EncryptionUtilsTest { /** * Validate encryption/decryption when given text is null @@ -24,8 +22,8 @@ void validateEncryptAndDecryptAES256GCMNullText() { void validateEncryptAndDecryptAES256GCMBlankText() { String keyEncryptionKey = "myKeyEncryptionKeyWrongSize"; - String stillBlankText = EncryptionUtils.encryptAES256GCM(EMPTY, keyEncryptionKey); - Assertions.assertEquals(EMPTY, stillBlankText); + String stillBlankText = EncryptionUtils.encryptAES256GCM("", keyEncryptionKey); + Assertions.assertEquals("", stillBlankText); } /** diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 4c3ba743..7a328ccd 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -22,10 +22,10 @@ ns4kafka: manage-acls: true manage-topics: true manage-connectors: true + connectsS: + test-connect: "toto" connects: test-connect: - url: "" #"http://localhost/kafka/connect/" - #basicAuthUsername: "user" - #basicAuthPassword: "password" + url: "localhost:8083" config: bootstrap.servers: "localhost:9092"