From bd7fc406bb04f6506581f00e95edc73e4d8cd603 Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Mon, 26 Aug 2024 16:41:43 +0200 Subject: [PATCH 01/13] Refactor: Switch to Gradle and Java --- .github/workflows/build-and-test-backend.yml | 50 ++-- .gradle/8.5/checksums/checksums.lock | Bin 0 -> 17 bytes .../dependencies-accessors.lock | Bin 0 -> 17 bytes .../8.5/dependencies-accessors/gc.properties | 0 .../8.5/executionHistory/executionHistory.bin | Bin 0 -> 19663 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .gradle/8.5/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/8.5/fileHashes/fileHashes.bin | Bin 0 -> 18697 bytes .gradle/8.5/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .gradle/8.5/gc.properties | 0 .gradle/8.7/checksums/checksums.lock | Bin 0 -> 17 bytes .../8.7/dependencies-accessors/gc.properties | 0 .../8.7/executionHistory/executionHistory.bin | Bin 0 -> 280533 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .gradle/8.7/expanded/expanded.lock | Bin 0 -> 17 bytes .gradle/8.7/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/8.7/fileHashes/fileHashes.bin | Bin 0 -> 25547 bytes .gradle/8.7/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../8.7/fileHashes/resourceHashesCache.bin | Bin 0 -> 20741 bytes .gradle/8.7/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 2 + .gradle/buildOutputCleanup/outputFiles.bin | Bin 0 -> 19289 bytes .gradle/file-system.probe | Bin 0 -> 8 bytes .gradle/vcs-1/gc.properties | 0 .../.mvn/wrapper/maven-wrapper.properties | 19 -- agentmanager/backend/mvnw | 259 ----------------- agentmanager/backend/mvnw.cmd | 149 ---------- agentmanager/backend/pom.xml | 262 ------------------ .../backend/AgentManagerConfiguration.kt | 45 --- .../gepard/backend/BackendApplication.kt | 15 - .../gepard/backend/application/AgentCache.kt | 26 -- .../backend/application/AgentService.kt | 36 --- .../backend/application/GenericCache.kt | 29 -- .../gepard/backend/domain/model/Agent.kt | 36 --- .../domain/valueObjects/AgentMetaData.kt | 8 - .../domain/valueObjects/Configuration.kt | 5 - .../domain/valueObjects/RegistrationTime.kt | 5 - .../incoming/AgentController.kt | 92 ------ .../src/main/resources/application.properties | 1 - .../openai/AgentController-1.0.0.yaml | 127 --------- .../application/BackendApplicationTest.kt | 12 - .../incoming/AgentControllerTest.kt | 89 ------ agentmanager/frontend/pom.xml | 91 ------ agentmanager/pom.xml | 49 ---- {agentmanager/backend => backend}/.gitignore | 0 backend/Dockerfile | 36 +++ backend/build.gradle | 61 ++++ backend/lombok.config | 2 + .../gepard/agentmanager/Application.java | 13 + .../agentmanager/agent/model/Agent.java | 32 +++ .../application/config/CorsConfiguration.java | 23 ++ .../controller/ConnectionController.java | 41 +++ .../connection/model/Connection.java | 29 ++ .../connection/model/ConnectionDtoMapper.java | 57 ++++ .../connection/model/dto/ConnectionDto.java | 37 +++ .../model/dto/CreateConnectionRequest.java | 15 + .../model/dto/CreateConnectionResponse.java | 24 ++ .../connection/service/ConnectionService.java | 56 ++++ .../agentmanager/exception/ApiError.java | 15 + .../exception/GlobalExceptionHandler.java | 86 ++++++ backend/src/main/resources/application.yaml | 18 ++ backend/src/main/resources/ssl/igc-dev.p12 | Bin 0 -> 2978 bytes .../controller/ConnectionControllerTest.java | 90 ++++++ .../model/ConnectionDtoMapperTest.java | 76 +++++ build.gradle | 7 + build/tmp/spotless-register-dependencies | 1 + .../frontend => frontend}/src/Api.js | 0 .../frontend => frontend}/src/README.md | 0 .../frontend => frontend}/src/app.vue | 0 .../src/assets/css/main.css | 0 .../src/components/AgentDashboard.vue | 0 .../src/components/AgentDetailHeader.vue | 0 .../src/components/AgentList.vue | 0 .../src/components/AgentSummary.vue | 0 .../src/components/AgentUpdate.vue | 0 .../src/components/ExternFileHandler.vue | 0 .../src/components/SidebarHeader.vue | 0 .../src/components/Stats.vue | 0 .../src/components/UpdateFeed.vue | 0 .../src/components/YamlPreviewer.vue | 0 .../src/modals/EditModal.vue | 0 .../frontend => frontend}/src/nuxt.config.ts | 0 .../src/package-lock.json | 0 .../frontend => frontend}/src/package.json | 0 .../src/pages/agents/[name].vue | 0 .../frontend => frontend}/src/pages/index.vue | 0 .../src/public/gepardLogo.png | Bin .../src/public/gepardLogo.webp | Bin .../src/server/tsconfig.json | 0 .../src/stores/Agentstore.js | 0 .../src/stores/FileStore.js | 0 .../src/tailwind.config.js | 0 .../frontend => frontend}/src/tsconfig.json | 0 .../frontend => frontend}/src/yarn.lock | 0 gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 +++++++++++++++++ gradlew.bat | 92 ++++++ http/connections.http | 41 +++ settings.gradle | 5 + 101 files changed, 1136 insertions(+), 1384 deletions(-) create mode 100644 .gradle/8.5/checksums/checksums.lock create mode 100644 .gradle/8.5/dependencies-accessors/dependencies-accessors.lock create mode 100644 .gradle/8.5/dependencies-accessors/gc.properties create mode 100644 .gradle/8.5/executionHistory/executionHistory.bin create mode 100644 .gradle/8.5/executionHistory/executionHistory.lock create mode 100644 .gradle/8.5/fileChanges/last-build.bin create mode 100644 .gradle/8.5/fileHashes/fileHashes.bin create mode 100644 .gradle/8.5/fileHashes/fileHashes.lock create mode 100644 .gradle/8.5/gc.properties create mode 100644 .gradle/8.7/checksums/checksums.lock create mode 100644 .gradle/8.7/dependencies-accessors/gc.properties create mode 100644 .gradle/8.7/executionHistory/executionHistory.bin create mode 100644 .gradle/8.7/executionHistory/executionHistory.lock create mode 100644 .gradle/8.7/expanded/expanded.lock create mode 100644 .gradle/8.7/fileChanges/last-build.bin create mode 100644 .gradle/8.7/fileHashes/fileHashes.bin create mode 100644 .gradle/8.7/fileHashes/fileHashes.lock create mode 100644 .gradle/8.7/fileHashes/resourceHashesCache.bin create mode 100644 .gradle/8.7/gc.properties create mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 .gradle/buildOutputCleanup/cache.properties create mode 100644 .gradle/buildOutputCleanup/outputFiles.bin create mode 100644 .gradle/file-system.probe create mode 100644 .gradle/vcs-1/gc.properties delete mode 100644 agentmanager/backend/.mvn/wrapper/maven-wrapper.properties delete mode 100755 agentmanager/backend/mvnw delete mode 100644 agentmanager/backend/mvnw.cmd delete mode 100644 agentmanager/backend/pom.xml delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/AgentManagerConfiguration.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/BackendApplication.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentCache.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentService.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/GenericCache.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/model/Agent.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/AgentMetaData.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/Configuration.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/RegistrationTime.kt delete mode 100644 agentmanager/backend/src/main/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentController.kt delete mode 100644 agentmanager/backend/src/main/resources/application.properties delete mode 100644 agentmanager/backend/src/main/resources/openai/AgentController-1.0.0.yaml delete mode 100644 agentmanager/backend/src/test/kotlin/rocks/gepard/backend/application/BackendApplicationTest.kt delete mode 100644 agentmanager/backend/src/test/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentControllerTest.kt delete mode 100644 agentmanager/frontend/pom.xml delete mode 100644 agentmanager/pom.xml rename {agentmanager/backend => backend}/.gitignore (100%) create mode 100644 backend/Dockerfile create mode 100644 backend/build.gradle create mode 100644 backend/lombok.config create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/Application.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandler.java create mode 100644 backend/src/main/resources/application.yaml create mode 100644 backend/src/main/resources/ssl/igc-dev.p12 create mode 100644 backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java create mode 100644 backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java create mode 100644 build.gradle create mode 100644 build/tmp/spotless-register-dependencies rename {agentmanager/frontend => frontend}/src/Api.js (100%) rename {agentmanager/frontend => frontend}/src/README.md (100%) rename {agentmanager/frontend => frontend}/src/app.vue (100%) rename {agentmanager/frontend => frontend}/src/assets/css/main.css (100%) rename {agentmanager/frontend => frontend}/src/components/AgentDashboard.vue (100%) rename {agentmanager/frontend => frontend}/src/components/AgentDetailHeader.vue (100%) rename {agentmanager/frontend => frontend}/src/components/AgentList.vue (100%) rename {agentmanager/frontend => frontend}/src/components/AgentSummary.vue (100%) rename {agentmanager/frontend => frontend}/src/components/AgentUpdate.vue (100%) rename {agentmanager/frontend => frontend}/src/components/ExternFileHandler.vue (100%) rename {agentmanager/frontend => frontend}/src/components/SidebarHeader.vue (100%) rename {agentmanager/frontend => frontend}/src/components/Stats.vue (100%) rename {agentmanager/frontend => frontend}/src/components/UpdateFeed.vue (100%) rename {agentmanager/frontend => frontend}/src/components/YamlPreviewer.vue (100%) rename {agentmanager/frontend => frontend}/src/modals/EditModal.vue (100%) rename {agentmanager/frontend => frontend}/src/nuxt.config.ts (100%) rename {agentmanager/frontend => frontend}/src/package-lock.json (100%) rename {agentmanager/frontend => frontend}/src/package.json (100%) rename {agentmanager/frontend => frontend}/src/pages/agents/[name].vue (100%) rename {agentmanager/frontend => frontend}/src/pages/index.vue (100%) rename {agentmanager/frontend => frontend}/src/public/gepardLogo.png (100%) rename {agentmanager/frontend => frontend}/src/public/gepardLogo.webp (100%) rename {agentmanager/frontend => frontend}/src/server/tsconfig.json (100%) rename {agentmanager/frontend => frontend}/src/stores/Agentstore.js (100%) rename {agentmanager/frontend => frontend}/src/stores/FileStore.js (100%) rename {agentmanager/frontend => frontend}/src/tailwind.config.js (100%) rename {agentmanager/frontend => frontend}/src/tsconfig.json (100%) rename {agentmanager/frontend => frontend}/src/yarn.lock (100%) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 http/connections.http create mode 100644 settings.gradle diff --git a/.github/workflows/build-and-test-backend.yml b/.github/workflows/build-and-test-backend.yml index 4bfc3db4..bfef4098 100644 --- a/.github/workflows/build-and-test-backend.yml +++ b/.github/workflows/build-and-test-backend.yml @@ -1,38 +1,30 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Backend CI +name: Gradle Build on: push: - branches: [ "*" ] - pull_request: - branches: [ "*" ] jobs: - build-and-test: + build: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: maven - - name: Build and test backend with Maven - run: mvn -B --update-snapshots package --file ./backend/pom.xml - - name: Copy build artifact to staging directory - run: mkdir staging && cp ./backend/target/*.jar staging - - name: Upload build artifact to packages - uses: actions/upload-artifact@v4 - with: - name: agentmanager-backend-snapshot - path: staging + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + with: + build-scan-publish: true + build-scan-terms-of-service-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-service-agree: "yes" + + - name: Build with Gradle + run: ./gradlew build diff --git a/.gradle/8.5/checksums/checksums.lock b/.gradle/8.5/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..2df13339566431a79415aa2dc23508f6fe9993d1 GIT binary patch literal 17 ScmZRsD&drhT*En$0SW*ig95Yw literal 0 HcmV?d00001 diff --git a/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000000000000000000000000000000000000..3a72896c791e16df09c1583295a697c92aee2b26 GIT binary patch literal 17 TcmZR!le;waJ45Vr1}FdkEqDX6 literal 0 HcmV?d00001 diff --git a/.gradle/8.5/dependencies-accessors/gc.properties b/.gradle/8.5/dependencies-accessors/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle/8.5/executionHistory/executionHistory.bin b/.gradle/8.5/executionHistory/executionHistory.bin new file mode 100644 index 0000000000000000000000000000000000000000..c1ac398b54ce2412ec70759a59659674004d019e GIT binary patch literal 19663 zcmeI%Pe>F|90%~X>lP+O3uOo$x@cwSj=JNY!~#vrz)I9DL1KA3^W1&8&Q5RM?pDyv zKvXoNLMp^dT_U@PSR_G2CWH{u#e+rADe97!@?f+7P@#j8hluaM%zMnA-=7cf?Pco; z$#buy53+jXu1+z700bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafK;VB7@Y#*zq?*Yq zawaCbF1>8ZxmGFJuj-ffSuv(i%=& z1rJ0ej~H8#YhA!UB)q#eVQYw%l~=SzyUDuG+OC=`dvZfP*RLr*@mWvZ>9aT2rtpby z)^tcp)2db!i-{Q5n3Y*Ic8VlJ2EbBM%c;1^Q(he4hUXeL2UEAsGhx|8Sv~fnA%n?y zM0BA%Yp2C_tdzW9on_U_yo7m^-y+p-H z9%ha?$L518ndOk~JhOf5n8a-yk>AFAy|=#hEOvap{cyBDnexrN(=}v@h`q}sXI3)x zhb>IK8k)Et-+M87;N$a_e6q5zmEBQhpcV2 zJ$Y~54{j!Ec64v$S@-8nzdZ1ecP(wb>VC=e=U%GU`;plT-5)o7v&w!(?eRqb0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_Kd zzX(KSI4Ebpncluk+PbRtyGN$7!PsWkw-7DstGQ%co!}XiYvgw#9a?xVCYEz=S$pPb zqg2odk4hX|HYX1AXNB&{>2!~%KepZ8g>^#ztRTLc6QilMos@6*xYsV8$3AK&Ix(cq I2z)w;A7#9F82|tP literal 0 HcmV?d00001 diff --git a/.gradle/8.5/fileHashes/fileHashes.lock b/.gradle/8.5/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..88332551db4ab5652a556806752e7498b569f268 GIT binary patch literal 17 TcmZQJ3tFo9PB_kh0Rq?nB!B|v literal 0 HcmV?d00001 diff --git a/.gradle/8.5/gc.properties b/.gradle/8.5/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle/8.7/checksums/checksums.lock b/.gradle/8.7/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..e8bdd909d31d4fdde3d3c0653c319530d3790285 GIT binary patch literal 17 TcmZQJn_7Q))z^}33{U_7J9Px= literal 0 HcmV?d00001 diff --git a/.gradle/8.7/dependencies-accessors/gc.properties b/.gradle/8.7/dependencies-accessors/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle/8.7/executionHistory/executionHistory.bin b/.gradle/8.7/executionHistory/executionHistory.bin new file mode 100644 index 0000000000000000000000000000000000000000..d3f0075dae70a4f2f03f8d2d936d5faf5c2345f6 GIT binary patch literal 280533 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 literal 0 HcmV?d00001 diff --git a/.gradle/8.7/expanded/expanded.lock b/.gradle/8.7/expanded/expanded.lock new file mode 100644 index 0000000000000000000000000000000000000000..412639b6b6de305dc160357c70e6370ffe6d09ad GIT binary patch literal 17 TcmZQ>;+?oD)qiFI0~7!NEGh$& literal 0 HcmV?d00001 diff --git a/.gradle/8.7/fileChanges/last-build.bin b/.gradle/8.7/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/.gradle/8.7/fileHashes/fileHashes.bin b/.gradle/8.7/fileHashes/fileHashes.bin new file mode 100644 index 0000000000000000000000000000000000000000..39cdd189d49ced87fc3850637181b83ed9bf0714 GIT binary patch literal 25547 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 literal 0 HcmV?d00001 diff --git a/.gradle/8.7/fileHashes/fileHashes.lock b/.gradle/8.7/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..03788648901993cc03bd92736ee0c8076e45786d GIT binary patch literal 17 VcmZQR+Zj`OIi0ha0SuTI001-q1e5>( literal 0 HcmV?d00001 diff --git a/.gradle/8.7/fileHashes/resourceHashesCache.bin b/.gradle/8.7/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000000000000000000000000000000000000..21f050aeffcfb864d5d88eda0cc9cd8564f7e6c0 GIT binary patch literal 20741 zcmeI3do+}31HdPd+(M;De7lyNrXpI~CuLl^n2RwZl}nUM_}uA>lF)sWXcL+(T_iJ= zvLtff#oCODu4>00;m8gfC``jr~oQ}3ZMe004jhApaQ4> zD)3J!uw!~6ieNIDt7Rk`ss{)JW98`$8+OeRU4^WqTJSdIB>ev1wAlZX%JH;zz-fJu zC+#ZuiDSoog>g&BlTXaQS6I_vijDIQ@|2IfvAmqLI>4zakY9}5Z0dA~TaJx$9P)H0 z^7Dl+W7Yv~cXWn7=Nvm_spbeceH`*k(`HdvnFSB?Yar)33X%@({WTNgnUM3uhTk*4 zy&VKN^Bm;2R?`mmd*7r1Zto8H{r~WmO)*>nCQuz9e=sb!ktHHwtdWq zg1qYJxurRGs~Uh$FMz!IX`8RRf$b{5neveHgY5I0PCp0(oWX#+&39i3%ehSmxSa*$ z&ypDlesUaaUaV`7znXj7NmtO~&GwIJ{LPOwx2Eg3OSqA|Z3azEYU{=H;FO^kQXa9dGC+nZkOI${RTaDJKo{#v=Yz^7$F9&r8g zb&D%S`hZisArFo>vV6R^yBToiDCE)Uw`Av>9qj;|aUSxR62t_o;7$6{YC{)0aO4LKm||%Q~(t~1yBK002M$5Pyti`6+i`00aO4L zKm||%Q~(t~1yBK002M$5Pyti`6+i`00aO4LKm||%Q~(wDe--#{ddHFndetRAYOA6h zbYH~vP+DKM8@1Fb{5lPF|7`U3djo&iwEMgwTBBY$qmM|;SC}=-JyJ|*v+-Vg;@I}ZI~M$?c&C1gQfbEMJ*bbnL<#@2c$+Hc;qt9zi`lrFX|9&*MwY#lc5j*l|*{ z()@Ui={voedwAnLtySfeW|r*QVwulervD4PAyi>Cee80*-2Y&-K7qtZ!W+j`ISa{K zy_Rm6lt1`1Kg17j{3wV}_iwdfCMq8>YgHMr#T!$v6GjER0fq2{sgf)W`Zsu^i@)=$ zc9!lhMh&-vR?Hj9#T&})|Bg?|o0GaoucBS1-f?=ph;)yj(#qiCFVRAQ%!)QAwT+AL z#-?p1exe`Z+gGuH|n_EPxGT(OZQI|=<6|W zTHy`)V6;oFqL7uB_)}#ewQ&+}7~50~4@4@2>`!TAhZ;3=@WwNj==L+o)t0r_6Rb~n z1WKOHmiqKH^yad!-6>q5xH9Palfc)Kbr4d<+~NzKYqR&MjvY)rm*)45fj4{_CNAlV zc~gXf)vsHqjybk)Zyp-lqY#orDrEyy2g*e<&o4^ME*cc}e>wiez1c)N$CR zcy+o~HfZrC(i|P#Z3TEEJb1f&@=3iBQq7Tcv+oUT@P?Oz@=|5?lKF>CLT*fyWuL-FGPL7rT5l9J(pa+YKx&M?HZEuzd37$& z4a+e&6SOZVc9y*;+j8vEcFII6nXft+K`(H?@5Ne^?VSJOYUxOMK<@nyOC;;2q>dvY z&V*!hbG~fdr^}sJ!k%W~ja=EB$BFZdAG(#WhQ1Yz58{o!)9aj4JTo6Ft#0kg7jk{@ z#+`rw@s~GYTBpW^eky!X2HsdR&!>U5*FmHA8#42YkwCJtKrvKNl&)h6jxQSbQn zI65Wb4V_{&Unl*cFQGSwtTJqZX1TB1TTiA3j=gPj&t2(verUo7KZeMIMDmasozr$b z&MG=K`y}2l=__FKWqveI<$PxDbpP!R-pK419qm-2UE%MkZ`nBi>nOaDp&jHr{QSP* z)ZKCu<*$}Z;EkypWno=qydAcuyiNT4RcE=PuKE02rCM)Ry|Q9(PH@f0Y5W);w>D?C z*wh-7)ixXUdYWIx8-K?gW`WPSP#YC(D+@)JP*272S?1Dh%^+G4xDnbnT1fVrBP6R{ zq(0k@RyUo_1&j9IlTXSt4 ky+yx&fRVQIStkBSTYlbiz|qWU`R)^0uUt2_SxSum0sl*&qW}N^ literal 0 HcmV?d00001 diff --git a/.gradle/8.7/gc.properties b/.gradle/8.7/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..1a932724159e1492e018ef130527df73f7dc12ca GIT binary patch literal 17 VcmZR6+VJ-?n_)6P0~jz$0RT581RVeX literal 0 HcmV?d00001 diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 00000000..d0ae76bc --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Mon Aug 26 14:05:28 CEST 2024 +gradle.version=8.7 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000000000000000000000000000000000000..3bd8695b3d4997eab496d2ea707d427f442755c7 GIT binary patch literal 19289 zcmeI%Ye-XZ7{~E>H}hVOMVX|iR7P63Oeu1T9=FuYGzg@WBz4%d5ObL*sS$X~$X=wP znWc%C7CIdwLD4MKCX~_e0tzKwu%L{h*2eiiO87vLje>(0Te(16hHwKKmim$0Te(16hHwKKmim$0Te(16!>2X#2Ma*4gY3D zKBmSUhirks*VeG(QydW4mHN@Mnm^11)Bgu$d*s$ds?%?Im)<5X&kyV!bbZXTK&l|G zQ1#W;iF1ee12PZt`uOqh%7>+CY<@C%L-W2qp)N9o_lUdX56n}BC1*dBGjAqu91L{tlpUFuE#=u*#OrK)!;^8)hbsf-g|LyCUphsjB61$$j0_S4LgzvHn@ie4O0P!^~oT!>xDBkC1zZD?2ssb?mxa zANlSM#f!9{QTo2!BoBzyCOipg3}N@vMSiS4uBJ$oreyP@$-_#|r`tG$v+sk_gapWP3qB~MIhkuRLQpULLzB~Oa+y3rnJ5yP(APoDZCA+P&rofljGg*>CFFj~@C z{gij^9(m@DCim?8k#4qrV96`1l4A9KhTpSslbS90v|6j0s@=>xH%Xo~v88J|%A$?= zH}c$T-M_1@B-3?c$>~)fF!$rn!7~b=01BW03ZMWApa2S>01BW03ZMWApa2S>01BW0 z3ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>z(y!=&~Ou*3=b3I!}w(Xj~HNs f%lh5(+9t2xSt}>6O;&!UtC_Q!v(~m|L!R{)Tx?9G literal 0 HcmV?d00001 diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000000000000000000000000000000000000..41544bb2abf45ea5e50324f9dd215989b0fa19bf GIT binary patch literal 8 PcmZQzV4T=@S35Ej& literal 0 HcmV?d00001 diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/agentmanager/backend/.mvn/wrapper/maven-wrapper.properties b/agentmanager/backend/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 8f96f52c..00000000 --- a/agentmanager/backend/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/agentmanager/backend/mvnw b/agentmanager/backend/mvnw deleted file mode 100755 index d7c358e5..00000000 --- a/agentmanager/backend/mvnw +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - 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" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/agentmanager/backend/mvnw.cmd b/agentmanager/backend/mvnw.cmd deleted file mode 100644 index 6f779cff..00000000 --- a/agentmanager/backend/mvnw.cmd +++ /dev/null @@ -1,149 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/agentmanager/backend/pom.xml b/agentmanager/backend/pom.xml deleted file mode 100644 index 9cf38faf..00000000 --- a/agentmanager/backend/pom.xml +++ /dev/null @@ -1,262 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.3.0 - - - rocks.gepard - backend - 0.0.1-SNAPSHOT - backend - backend - - 17 - 1.9.0 - - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-web - - - - com.fasterxml.jackson.module - jackson-module-kotlin - - - - org.jetbrains.kotlin - kotlin-reflect - - - - org.jetbrains.kotlin - kotlin-stdlib-jdk8 - ${kotlin.version} - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - - com.h2database - h2 - runtime - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.jetbrains.kotlin - kotlin-test-junit5 - test - - - - com.google.code.gson - gson - 2.10.1 - test - - - - io.swagger.core.v3 - swagger-annotations - 2.2.2 - - - - org.openapitools - jackson-databind-nullable - 0.2.2 - - - - javax.validation - validation-api - 2.0.1.Final - - - - javax.servlet - javax.servlet-api - 3.0.1 - compile - - - - javax.annotation - javax.annotation-api - 1.3.2 - - - - org.apache.commons - commons-lang3 - 3.14.0 - - - org.jetbrains.kotlin - kotlin-test - ${kotlin.version} - test - - - - - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/test/kotlin - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - compile - - - - src/main/kotlin - target/generated-sources/annotations - target/generated-sources/openapi/src/main/java - - - - - test-compile - test-compile - - test-compile - - - - src/test/kotlin - target/generated-test-sources/test-annotations - - - - - - - -Xjsr305=strict - - - spring - jpa - - 1.8 - - - - org.jetbrains.kotlin - kotlin-maven-allopen - ${kotlin.version} - - - org.jetbrains.kotlin - kotlin-maven-noarg - ${kotlin.version} - - - - - - org.openapitools - openapi-generator-maven-plugin - 6.1.0 - - - - generate - - - - ${project.basedir}/src/main/resources/openai/AgentController-1.0.0.yaml - - spring - rocks.gepard.backend.infrastructure.incoming.api - rocks.gepard.backend.infrastructure.incoming.model - - true - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - default-compile - none - - - default-testCompile - none - - - compile - compile - - compile - - - - testCompile - test-compile - - testCompile - - - - - - - - - diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/AgentManagerConfiguration.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/AgentManagerConfiguration.kt deleted file mode 100644 index 71b3c872..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/AgentManagerConfiguration.kt +++ /dev/null @@ -1,45 +0,0 @@ -package rocks.gepard.backend - -import org.springframework.boot.ApplicationArguments -import org.springframework.boot.ApplicationRunner -import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.boot.web.servlet.FilterRegistrationBean -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.core.Ordered -import org.springframework.web.cors.CorsConfiguration -import org.springframework.web.cors.UrlBasedCorsConfigurationSource -import org.springframework.web.filter.CorsFilter -import java.util.function.Consumer - -@Configuration -@EnableConfigurationProperties -class AgentManagerConfiguration { - // Bootstrap some test data into the in-memory database -// @Bean -// fun init(repository: AgentRepository): ApplicationRunner { -// return ApplicationRunner { _: ApplicationArguments? -> -// arrayOf("Affe", "Häschen", "Schildkröte", "Hund", "Katze").forEach { -// val agent = Agent(it) -// repository.save(agent) -// } -// repository.findAll().forEach(/* action = */ Consumer { x: Agent? -> println(x) }) -// } -// } - - // Fix the CORS errors - @Bean - fun simpleCorsFilter(): FilterRegistrationBean<*> { - val source = UrlBasedCorsConfigurationSource() - val config = CorsConfiguration() - config.allowCredentials = true - // *** URL below needs to match the Vue client URL and port *** - config.allowedOrigins = listOf("http://localhost:3000") - config.allowedMethods = listOf("*") - config.allowedHeaders = listOf("*") - source.registerCorsConfiguration("/**", config) - val bean: FilterRegistrationBean<*> = FilterRegistrationBean(CorsFilter(source)) - bean.order = Ordered.HIGHEST_PRECEDENCE - return bean - } -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/BackendApplication.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/BackendApplication.kt deleted file mode 100644 index 2e6f4ead..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/BackendApplication.kt +++ /dev/null @@ -1,15 +0,0 @@ -package rocks.gepard.backend - -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.context.properties.ConfigurationProperties - -@SpringBootApplication -class BackendApplication { - companion object { - @JvmStatic - fun main(args: Array) { - SpringApplication.run(BackendApplication::class.java, *args) - } - } -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentCache.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentCache.kt deleted file mode 100644 index 62b27fec..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentCache.kt +++ /dev/null @@ -1,26 +0,0 @@ -package rocks.gepard.backend.application - -import rocks.gepard.backend.infrastructure.incoming.model.AgentResponseDto - - -class AgentCache : GenericCache { - - private val cache = HashMap() - - override val size: Int - get() = cache.size - - override fun set(key: K, value: V) { - cache[key] = value - } - - override fun remove(key: K) = cache.remove(key) - - override fun get(key: K) = cache[key] - - override fun clear() = cache.clear() - - fun getAllAgents() : MutableList{ - return TODO() - } -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentService.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentService.kt deleted file mode 100644 index 21a31416..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/AgentService.kt +++ /dev/null @@ -1,36 +0,0 @@ -package rocks.gepard.backend.application - -import org.springframework.stereotype.Service -import rocks.gepard.backend.domain.model.Agent -import rocks.gepard.backend.infrastructure.incoming.model.AgentDto -import rocks.gepard.backend.infrastructure.incoming.model.AgentResponseDto - -@Service -class AgentService { - - private val agentCache: AgentCache = AgentCache() - - fun storeAgent(agent: Agent?) { - agent?.let { - agentCache.set(it.name, it) - } - } - - fun getAllAgents(): MutableList { - - return agentCache.getAllAgents() - } - - fun getAgentByName(agentName: String?): AgentResponseDto? { - return agentName - ?.let { agentCache.get(it) } - ?.let(Agent::toResponseDto) - } -} - -fun Agent.toResponseDto(): AgentResponseDto { - return AgentResponseDto().name(this.name) - .javaversion(this.getJavaVersion()) - .otelversion(this.getOtelVersion()) - .config(this.configuration.toString()) -} diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/GenericCache.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/GenericCache.kt deleted file mode 100644 index 09aae274..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/application/GenericCache.kt +++ /dev/null @@ -1,29 +0,0 @@ -package rocks.gepard.backend.application - -interface GenericCache { - - /** - * The number of the items that are currently cached. - */ - val size: Int - - /** - * Cache a [value] with a given [key] - */ - operator fun set(key: K, value: V) - - /** - * Get the cached value of a given [key], or null if it's not cached or evicted. - */ - operator fun get(key: K): V? - - /** - * Remove the value of the [key] from the cache, and return the removed value, or null if it's not cached at all. - */ - fun remove(key: K): V? - - /** - * Remove all the items in the cache. - */ - fun clear() -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/model/Agent.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/model/Agent.kt deleted file mode 100644 index b469bdfa..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/model/Agent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package rocks.gepard.backend.domain.model - -import jakarta.persistence.GeneratedValue -import jakarta.persistence.Id -import rocks.gepard.backend.domain.valueObjects.AgentMetaData -import rocks.gepard.backend.domain.valueObjects.Configuration -import java.time.LocalDateTime - -//@Entity -class Agent( - var name: String, - var registrationTime: LocalDateTime = LocalDateTime.now(), - var agentMetadata: AgentMetaData, -) { - - @Id - @GeneratedValue - var id: Long? = null - - val configuration: Configuration - get() = Configuration("configuration") - - fun getJavaVersion(): String { - return agentMetadata.javaVersion - } - - fun getOtelVersion(): String { - return agentMetadata.otelVersion - } - - override fun toString(): String { - return "Name='$name', Registrationtime='$registrationTime', " + - "agentMetaData='$agentMetadata'" - } - -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/AgentMetaData.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/AgentMetaData.kt deleted file mode 100644 index 4034b4ed..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/AgentMetaData.kt +++ /dev/null @@ -1,8 +0,0 @@ -package rocks.gepard.backend.domain.valueObjects - -class AgentMetaData(var otelVersion : String, var javaVersion : String) { - override fun toString(): String { - return "otelVersion='$otelVersion', " + - "javaVersion='$javaVersion'" - } -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/Configuration.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/Configuration.kt deleted file mode 100644 index c194260a..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/Configuration.kt +++ /dev/null @@ -1,5 +0,0 @@ -package rocks.gepard.backend.domain.valueObjects - -class Configuration(configuration: String) { - -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/RegistrationTime.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/RegistrationTime.kt deleted file mode 100644 index 31062bca..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/domain/valueObjects/RegistrationTime.kt +++ /dev/null @@ -1,5 +0,0 @@ -package rocks.gepard.backend.domain.valueObjects - -class RegistrationTime(var value:Long) { - -} \ No newline at end of file diff --git a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentController.kt b/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentController.kt deleted file mode 100644 index a8375a4f..00000000 --- a/agentmanager/backend/src/main/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentController.kt +++ /dev/null @@ -1,92 +0,0 @@ -package rocks.gepard.backend.infrastructure.incoming - -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController -import rocks.gepard.backend.application.AgentService -import rocks.gepard.backend.domain.model.Agent -import rocks.gepard.backend.domain.valueObjects.AgentMetaData -import rocks.gepard.backend.infrastructure.incoming.api.AgentsApi -import rocks.gepard.backend.infrastructure.incoming.model.AgentDto -import rocks.gepard.backend.infrastructure.incoming.model.AgentResponseDto -import java.net.URI - -@RestController -class AgentController : AgentsApi { - - private val agentService = AgentService() - private val agentList: MutableList = mutableListOf() - - init { - createTestDate() - } - - override fun registerAgent(agentDto: AgentDto?): ResponseEntity { - val agentResponseDto = agentDto?.toResponse() - - // first without deeper logic -// val agent = agentDto?.toDomain() -// agentService.storeAgent(agent) - agentResponseDto?.let { - agentList.add(0, agentResponseDto) -// } else { -// agentResponseDto?.let { -// agentList.add(0, agentResponseDto) -// } - } - - return agentDto?.let { - ResponseEntity.created(URI.create("blubb")).build() - } ?: ResponseEntity.badRequest().build() - } - - override fun getAllAgents(limit: Int?): ResponseEntity> { -// val agents = agentService.getAllAgents() -// return ResponseEntity.ok(agents) - return ResponseEntity.ok(agentList) - } - - private fun createTestDate() { - agentList.clear() - val agent1 = AgentResponseDto() - agent1.name("Huhn") - agent1.healthState("alive") - agent1.javaversion("21") - agent1.otelversion("1.0.1") - agentList.add(agent1) - - val agent2 = AgentResponseDto() - agent2.name("Ente") - agent2.javaversion("8") - agent2.otelversion("1.0.5") - agentList.add(agent2) - - val agent3 = AgentResponseDto() - agent3.name("Rind") - agent3.healthState("alive") - agent3.config("config") - agent3.javaversion("17") - agent3.otelversion("1.0.4") - agentList.add(agent3) - } -} - -fun AgentDto.toDomain(): Agent { - return Agent( - name = this.name, - agentMetadata = AgentMetaData( - otelVersion = this.otelVersion, - javaVersion = this.javaVersion - ) - ) -} - -fun AgentDto.toResponse(): AgentResponseDto { - return AgentResponseDto().apply { - name = this@toResponse.name - healthState = "alive" - javaversion = this@toResponse.javaVersion - otelversion = this@toResponse.otelVersion - gepardVersion = this@toResponse.gepardVersion - } -} - diff --git a/agentmanager/backend/src/main/resources/application.properties b/agentmanager/backend/src/main/resources/application.properties deleted file mode 100644 index 3ca17a4e..00000000 --- a/agentmanager/backend/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=backend diff --git a/agentmanager/backend/src/main/resources/openai/AgentController-1.0.0.yaml b/agentmanager/backend/src/main/resources/openai/AgentController-1.0.0.yaml deleted file mode 100644 index 0a682699..00000000 --- a/agentmanager/backend/src/main/resources/openai/AgentController-1.0.0.yaml +++ /dev/null @@ -1,127 +0,0 @@ -openapi: "3.0.0" -info: - title: AgentController API Description - description: API for managing agents - contact: - email: jwt@novatec-gmbh.de - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - version: "1.0.0" -servers: - - url: "https://AgentController.swagger.io/api/v1" -tags: - - name: agent - description: Managing Agentlifecycle - -paths: - /agents: - post: - tags: - - agents - summary: Add a agent to the configserver - description: Add a agent to the configserver - operationId: registerAgent - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/AgentDto' - required: true - responses: - '201': - description: Successfully created - content: - application/json: - schema: - $ref: '#/components/schemas/AgentResponseDto' - '415': - description: Invalid input - get: - summary: List all agents - operationId: getAllAgents - tags: - - agents - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - maximum: 100 - format: int32 - responses: - '200': - description: A paged array of agents - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/ResponseAgents" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - -components: - schemas: - AgentDto: - type: object - properties: - name: - type: string - otelVersion: - type: string - gepardVersion: - type: string - javaVersion: - type: string - required: - - name - - AgentResponseDto: - type: object - properties: - name: - type: string - healthState: - type: string - otelversion: - type: string - javaversion: - type: string - config: - type: string - gepardVersion: - type: string - required: - - name - - healthState - - javaversion - - otelversion - - config - - gepardVersion - - ResponseAgents: - type: array - maxItems: 100 - items: - $ref: "#/components/schemas/AgentResponseDto" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/agentmanager/backend/src/test/kotlin/rocks/gepard/backend/application/BackendApplicationTest.kt b/agentmanager/backend/src/test/kotlin/rocks/gepard/backend/application/BackendApplicationTest.kt deleted file mode 100644 index 3ccf1cde..00000000 --- a/agentmanager/backend/src/test/kotlin/rocks/gepard/backend/application/BackendApplicationTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package rocks.gepard.backend.application - -import org.junit.jupiter.api.Test - -import org.junit.jupiter.api.Assertions.* - -class BackendApplicationTest { - - @Test - fun main() { - } -} \ No newline at end of file diff --git a/agentmanager/backend/src/test/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentControllerTest.kt b/agentmanager/backend/src/test/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentControllerTest.kt deleted file mode 100644 index 4395338e..00000000 --- a/agentmanager/backend/src/test/kotlin/rocks/gepard/backend/infrastructure/incoming/AgentControllerTest.kt +++ /dev/null @@ -1,89 +0,0 @@ -package rocks.gepard.backend.infrastructure.incoming - -import com.google.gson.Gson -import org.apache.commons.lang3.StringUtils -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultMatchers -import rocks.gepard.backend.infrastructure.incoming.model.AgentDto - -@WebMvcTest(AgentController::class) -class AgentControllerTest { - - @Autowired - private lateinit var mvc: MockMvc - - @Test - fun registerAgentWillReturns201IfAgentsWasCreated() { - - //GIVEN - val agentDto = AgentDto() - agentDto.name = "Hase" - agentDto.javaVersion = "17" - agentDto.otelVersion = "21" - - var gson = Gson() - val agentJson = gson.toJson(agentDto) - - //WHEN || THEN - mvc.perform( - MockMvcRequestBuilders.post("/api/v1/registerAgent") - .contentType(MediaType.APPLICATION_JSON) - .content(agentJson) - ) - .andExpect(MockMvcResultMatchers.status().isCreated) - } - - @Test - fun registerAgentWillReturns400WhenContentIsEmpty() { - - //GIVEN || WHEN || THEN - mvc.perform( - MockMvcRequestBuilders.post("/api/v1/registerAgent") - .contentType(MediaType.APPLICATION_JSON) - .content(StringUtils.EMPTY) - ) - .andExpect(MockMvcResultMatchers.status().isBadRequest) - } - - @Test - fun registerAgentWillReturns415WhenContentIsXML() { - - //GIVEN - val agentDto = AgentDto() - agentDto.name = "Hase" - agentDto.javaVersion = "17" - agentDto.otelVersion = "21" - - var gson = Gson() - val agentJson = gson.toJson(agentDto) - - //WHEN || THEN - mvc.perform( - MockMvcRequestBuilders.post("/api/v1/registerAgent") - .contentType(MediaType.APPLICATION_XML) - .content(agentJson) - ) - .andExpect(MockMvcResultMatchers.status().isUnsupportedMediaType) - } - - @Test - fun registerAgentWillReturns415WhenContentIsNull() { - - //GIVEN - var gson = Gson() - val agentJson = gson.toJson(null) - - //WHEN || THEN - mvc.perform( - MockMvcRequestBuilders.post("/api/v1/registerAgent") - .contentType(MediaType.APPLICATION_XML) - .content(agentJson) - ) - .andExpect(MockMvcResultMatchers.status().isUnsupportedMediaType) - } -} \ No newline at end of file diff --git a/agentmanager/frontend/pom.xml b/agentmanager/frontend/pom.xml deleted file mode 100644 index 7d42cbc5..00000000 --- a/agentmanager/frontend/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - 4.0.0 - - rocks.gepard - agentmanager - 1.0-SNAPSHOT - - - frontend - - - UTF-8 - official - 1.8 - - - - - mavenCentral - https://repo1.maven.org/maven2/ - - - - - blubb/main/kotlin - blubb/test/kotlin - - - org.jetbrains.kotlin - kotlin-maven-plugin - 1.9.21 - - - compile - compile - - compile - - - - test-compile - test-compile - - test-compile - - - - - - maven-surefire-plugin - 2.22.2 - - - maven-failsafe-plugin - 2.22.2 - - - org.codehaus.mojo - exec-maven-plugin - 1.6.0 - - MainKt - - - - - - - - org.jetbrains.kotlin - kotlin-test-junit5 - 1.9.21 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.10.0 - test - - - org.jetbrains.kotlin - kotlin-stdlib - 1.9.21 - - - - \ No newline at end of file diff --git a/agentmanager/pom.xml b/agentmanager/pom.xml deleted file mode 100644 index 87e16a58..00000000 --- a/agentmanager/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - 4.0.0 - - rocks.gepard - agentmanager - 1.0-SNAPSHOT - pom - - frontend - backend - - - - UTF-8 - - - - - mavenCentral - https://repo1.maven.org/maven2/ - - - - - - - maven-surefire-plugin - 2.22.2 - - - maven-failsafe-plugin - 2.22.2 - - - - - - - org.junit.jupiter - junit-jupiter-engine - 5.10.0 - test - - - - \ No newline at end of file diff --git a/agentmanager/backend/.gitignore b/backend/.gitignore similarity index 100% rename from agentmanager/backend/.gitignore rename to backend/.gitignore diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..a3080ffb --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,36 @@ +# ---- Build Stage ---- +FROM gradle:jdk21 as build + +WORKDIR /home/gradle/project + +# Copy the Gradle wrapper files +COPY ../gradle /home/gradle/project/gradle +COPY ../gradlew /home/gradle/project/ +COPY ../build.gradle /home/gradle/project/ +COPY ../settings.gradle /home/gradle/project/ + +# Copy server source code +COPY backend /home/gradle/project/backend + +# Build the application +RUN ./gradlew build -x test + +# ---- Runtime Stage ---- +# We use Temurin as it is the default at VHV +FROM eclipse-temurin:21-jre-jammy + +# In order to use a postgres database, we need to set the active profile +# ENV SPRING_PROFILES_ACTIVE=postgres + +# Copy the build from the server subproject to the runtime image +# The jar already contains the static export from the ui subproject +COPY --from=build /home/gradle/project/backend/build/libs/*.jar app.jar + +# Expose the port on which the app will run +EXPOSE 8080 + +# Run the application +ENTRYPOINT ["java", "-jar", "/app.jar"] + + + diff --git a/backend/build.gradle b/backend/build.gradle new file mode 100644 index 00000000..5703d34b --- /dev/null +++ b/backend/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.3.0' + id 'io.spring.dependency-management' version '1.1.5' + id("com.diffplug.spotless") version "6.25.0" + id 'jacoco' +} + +group = 'rocks.inspectit.gepard' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + + // Starters + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-validation' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + finalizedBy jacocoTestReport + useJUnitPlatform() +} + +jacocoTestReport { + dependsOn test +} + +spotless { + java { + importOrder() + removeUnusedImports() + + // Cleanthat will refactor code. Has to be configured. + // cleanthat() + + googleJavaFormat() // has its own section below + + formatAnnotations() // fixes formatting of type annotations, see below + + licenseHeader '/* (C) 2024 */' // or licenseHeaderFile + } +} + diff --git a/backend/lombok.config b/backend/lombok.config new file mode 100644 index 00000000..84bda564 --- /dev/null +++ b/backend/lombok.config @@ -0,0 +1,2 @@ +# Let static analysis tools know which classes are generated by Lombok +lombok.addLombokGeneratedAnnotation=true \ No newline at end of file diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/Application.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/Application.java new file mode 100644 index 00000000..9c03276a --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/Application.java @@ -0,0 +1,13 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java new file mode 100644 index 00000000..6d6c8c14 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java @@ -0,0 +1,32 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.agent.model; + +import jakarta.annotation.Nonnull; +import java.time.Instant; +import lombok.*; + +/** Represents an agent which is connected to the config server. */ +@RequiredArgsConstructor +@NoArgsConstructor +@Builder +@ToString +@Getter +public class Agent { + /** The name of the service which is running the agent. */ + @Nonnull private String serviceName; + + /** The process id of the JVM which carries the agent. */ + @Nonnull private Long pid; + + /** The Gepard-Version. */ + @Nonnull private String gepardVersion; + + /** The OpenTelemetry-Java-Instrumentation-Version. */ + @Nonnull private String otelVersion; + + /** The start time of the JVM which carries the agent. */ + @Nonnull private Instant startTime; + + /** The Java version of the JVM which carries the agent. */ + @Nonnull private String javaVersion; +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java new file mode 100644 index 00000000..950034b5 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java @@ -0,0 +1,23 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.application.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** Web configuration for the application. */ +@Configuration +public class CorsConfiguration implements WebMvcConfigurer { + + // TODO: Get CORS config from application.yaml + + /** + * Adds CORS mappings to the registry. Allow all origins and methods. + * + * @param registry + */ + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("*").allowedMethods("*"); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java new file mode 100644 index 00000000..8b5d0abe --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java @@ -0,0 +1,41 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.controller; + +import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; +import rocks.inspectit.gepard.agentmanager.connection.service.ConnectionService; + +/** + * Controller for handling agent connection requests. Holds the POST endpoint for handling + * connection requests from agents. + */ +@RestController +@RequestMapping("/api/v1/connections") +@RequiredArgsConstructor +public class ConnectionController { + + private final ConnectionService connectionService; + + @PostMapping + public ResponseEntity connect( + @Valid @RequestBody CreateConnectionRequest connectRequest) { + return ResponseEntity.ok(connectionService.handleConnectRequest(connectRequest)); + } + + @GetMapping + public ResponseEntity> getConnections() { + return ResponseEntity.ok(connectionService.getConnections()); + } + + @GetMapping("/{id}") + public ResponseEntity getConnection(@PathVariable UUID id) { + return ResponseEntity.ok(connectionService.getConnection(id)); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java new file mode 100644 index 00000000..bb43082a --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java @@ -0,0 +1,29 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.model; + +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.*; +import rocks.inspectit.gepard.agentmanager.agent.model.Agent; + +/** + * Represents a connected agent. It is an internal data structure and not exposed to the API. Acts + * as Aggregate Root. + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class Connection { + + /** The id of the connection. */ + private UUID id; + + /** The registration time * */ + private LocalDateTime registrationTime; + + /** The agent which is connected. */ + private Agent agent; +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java new file mode 100644 index 00000000..6056b005 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java @@ -0,0 +1,57 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.model; + +import java.time.Instant; +import java.util.UUID; +import org.springframework.stereotype.Component; +import rocks.inspectit.gepard.agentmanager.agent.model.Agent; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; + +/** + * This class is responsible for mapping Connection objects to ConnectionResponse and + * ConnectionRequest objects. It is annotated with @Component to be automatically detected by Spring + * for dependency injection. + */ +@Component +public class ConnectionDtoMapper { + + /** + * Maps a Connection object to a ConnectionSuccessfulResponse object. + * + * @param connection The Connection object to be mapped. + * @return A ConnectionSuccessfulResponse object containing the mapped data from the Connection + * object. + */ + public CreateConnectionResponse toCreateConnectionResponse(Connection connection) { + return new CreateConnectionResponse( + connection.getId(), + connection.getAgent().getServiceName(), + connection.getAgent().getGepardVersion(), + connection.getAgent().getOtelVersion(), + connection.getAgent().getPid(), + connection.getAgent().getStartTime().toEpochMilli(), + connection.getAgent().getJavaVersion()); + } + + /** + * Maps a CreateConnectionRequest object to a Connection object. + * + * @param request The CreateConnectionRequest object to be mapped. + * @return A Connection object containing the mapped data from the CreateConnectionRequest object. + */ + public Connection toConnection(CreateConnectionRequest request) { + return Connection.builder() + .id(UUID.randomUUID()) + .agent( + Agent.builder() + .serviceName(request.serviceName()) + .gepardVersion(request.gepardVersion()) + .otelVersion(request.otelVersion()) + .pid(request.pid()) + .startTime(Instant.ofEpochMilli(request.startTime())) + .javaVersion(request.javaVersion()) + .build()) + .build(); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java new file mode 100644 index 00000000..d736e953 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java @@ -0,0 +1,37 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.model.dto; + +import java.util.UUID; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; + +/** + * Represents a connection response for the UI, when reading connections. + * + * @param id + * @param serviceName + * @param gepardVersion + * @param otelVersion + * @param pid + * @param startTime + * @param javaVersion + */ +public record ConnectionDto( + UUID id, + String serviceName, + String gepardVersion, + String otelVersion, + Long pid, + Long startTime, + String javaVersion) { + + public static ConnectionDto fromConnection(Connection connection) { + return new ConnectionDto( + connection.getId(), + connection.getAgent().getServiceName(), + connection.getAgent().getGepardVersion(), + connection.getAgent().getOtelVersion(), + connection.getAgent().getPid(), + connection.getAgent().getStartTime().toEpochMilli(), + connection.getAgent().getJavaVersion()); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java new file mode 100644 index 00000000..e6a3adec --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java @@ -0,0 +1,15 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.model.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +/** Represents a connection request from an agent. */ +@Builder +public record CreateConnectionRequest( + @NotNull(message = "Service Name missing.") String serviceName, + @NotNull(message = "Gepard Version missing.") String gepardVersion, + @NotNull(message = "Open-Telemetry Version missing.") String otelVersion, + @NotNull(message = "Process ID is missing.") Long pid, + @NotNull(message = "Start-Time missing.") Long startTime, + @NotNull(message = "Java Version missing.") String javaVersion) {} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java new file mode 100644 index 00000000..d64a383b --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java @@ -0,0 +1,24 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.model.dto; + +import java.util.UUID; + +/** + * Represents a successful connection response. + * + * @param id + * @param serviceName + * @param gepardVersion + * @param otelVersion + * @param pid + * @param startTime + * @param javaVersion + */ +public record CreateConnectionResponse( + UUID id, + String serviceName, + String gepardVersion, + String otelVersion, + Long pid, + Long startTime, + String javaVersion) {} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java new file mode 100644 index 00000000..e5847480 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java @@ -0,0 +1,56 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.service; + +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; +import rocks.inspectit.gepard.agentmanager.connection.model.ConnectionDtoMapper; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; + +/** Service-Implementation for handling agent connection requests. */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ConnectionService { + + private final ConnectionDtoMapper connectionDtoMapper; + + private final HashMap connectionCache = new HashMap<>(); + + /** + * Handles a connection request from an agent. + * + * @param connectRequest + * @return + */ + public CreateConnectionResponse handleConnectRequest(CreateConnectionRequest connectRequest) { + Connection connection = connectionDtoMapper.toConnection(connectRequest); + connectionCache.put(connection.getId(), connection); + + return connectionDtoMapper.toCreateConnectionResponse(connection); + } + + public List getConnections() { + return connectionCache.values().stream().map(ConnectionDto::fromConnection).toList(); + } + + /** + * Returns a connection by its id. + * + * @param id The id of the connection. + * @return ReadConnectionDTO The connection. + */ + public ConnectionDto getConnection(UUID id) { + if (!connectionCache.containsKey(id)) { + throw new NoSuchElementException("No connection with id " + id + " found in cache."); + } + return ConnectionDto.fromConnection(connectionCache.get(id)); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java new file mode 100644 index 00000000..9b7d0abc --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java @@ -0,0 +1,15 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.exception; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * Represents an error that occurred during an API request. + * + * @param path + * @param errors + * @param statusCode + * @param timestamp + */ +public record ApiError(String path, List errors, int statusCode, LocalDateTime timestamp) {} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandler.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..b122c50d --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandler.java @@ -0,0 +1,86 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.exception; + +import jakarta.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; +import java.util.List; +import java.util.NoSuchElementException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +/** Global exception handler for the application. */ +@ControllerAdvice +public class GlobalExceptionHandler { + + /** + * Handles MethodArgumentNotValidException (e.g. if a NotNull-Field isn´t present). + * + * @param ex the exception + * @return the response entity + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationErrors( + MethodArgumentNotValidException ex, HttpServletRequest request) { + List errors = + ex.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).toList(); + + ApiError apiError = + new ApiError( + request.getRequestURI(), errors, HttpStatus.BAD_REQUEST.value(), LocalDateTime.now()); + + return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST); + } + + /** + * Handles HttpMessageNotReadableException (e.g. if no request body is provided). + * + * @param ex the exception + * @return the response entity + */ + @ExceptionHandler({HttpMessageNotReadableException.class}) + public ResponseEntity handleBadRequestError( + HttpMessageNotReadableException ex, HttpServletRequest request) { + ApiError apiError = + new ApiError( + request.getRequestURI(), + List.of(ex.getMessage()), + HttpStatus.BAD_REQUEST.value(), + LocalDateTime.now()); + return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST); + } + + /** + * Handles MethodArgumentTypeMismatchException (e.g. if a path variable is not of the correct + * type). + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatch( + MethodArgumentTypeMismatchException ex, HttpServletRequest request) { + ApiError apiError = + new ApiError( + request.getRequestURI(), + List.of(ex.getMessage()), + HttpStatus.BAD_REQUEST.value(), + LocalDateTime.now()); + return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST); + } + + /** Handles NoSuchElementException (e.g. if an entity is not found). */ + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleNotFoundError( + NoSuchElementException ex, HttpServletRequest request) { + ApiError apiError = + new ApiError( + request.getRequestURI(), + List.of(ex.getMessage()), + HttpStatus.NOT_FOUND.value(), + LocalDateTime.now()); + return new ResponseEntity<>(apiError, HttpStatus.NOT_FOUND); + } +} diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml new file mode 100644 index 00000000..049eb173 --- /dev/null +++ b/backend/src/main/resources/application.yaml @@ -0,0 +1,18 @@ +spring: + application: + name: backend + ssl: + bundle: + jks: + server: + key: + alias: "igc-dev" + keystore: + location: "classpath:ssl/igc-dev.p12" + password: "password" + type: "PKCS12" +server: + port: 8080 + ssl: + bundle: "server" + enabled-protocols: "TLSv1.3" \ No newline at end of file diff --git a/backend/src/main/resources/ssl/igc-dev.p12 b/backend/src/main/resources/ssl/igc-dev.p12 new file mode 100644 index 0000000000000000000000000000000000000000..a69439e03424bfd3649310cd70f67e64e855e1c1 GIT binary patch literal 2978 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b82aa23a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# 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. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# 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/HEAD/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 +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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +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 ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + 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 +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +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" || "$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 + 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 + # 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 +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +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. +# + +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 new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +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 %* + +:end +@rem End local scope for the variables with windows NT shell +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! +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 + +:omega diff --git a/http/connections.http b/http/connections.http new file mode 100644 index 00000000..78772d47 --- /dev/null +++ b/http/connections.http @@ -0,0 +1,41 @@ +# This is a collection of Http-Requests to the connections ressource. +# It can be used with IntelliJs HttpClient to do End-to-End-Testing of the Config-Server Endpoints. +# Run the server and just start the collection. + +### GET request to retrieve all connections +GET https://localhost:8080/api/v1/connections + +### POST request to connections without fields + +POST https://127.0.0.1:8080/api/v1/connections + +### POST request to connections with missing fields + POST https://127.0.0.1:8080/api/v1/connections +Content-Type: application/json + +{ + "javaVersion": "17", + "gepardVersion": "0.0.1-SNAPSHOT", + "startTime": 1719850971, + "pid": 432423 + +} + +### POST request to connections with all fields + POST https://127.0.0.1:8080/api/v1/connections +Content-Type: application/json + +{ + "serviceName": "test-service", + "otelVersion": "1.2.5", + "javaVersion": "17", + "gepardVersion": "0.0.1-SNAPSHOT", + "startTime": 1719850971, + "pid": 432423 +} + +> {% client.global.set("agent_id", response.body.id); %} + +### GET request to connections/{id} +GET https://localhost:8080/api/v1/connections/{{agent_id}} + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..10da18cb --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'inspectit-gepard-agentmanager' +include 'backend' +// Switch between the two client side projects. +// include 'client' +include 'frontend' \ No newline at end of file From 4def7d9f8a71c3ee486b426ddab4ce5828019efb Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Mon, 26 Aug 2024 16:52:42 +0200 Subject: [PATCH 02/13] Chore: Cleanup gitignore --- .gitignore | 37 +++++++++++++++--- .gradle/8.5/checksums/checksums.lock | Bin 17 -> 0 bytes .../dependencies-accessors.lock | Bin 17 -> 0 bytes .../8.5/dependencies-accessors/gc.properties | 0 .../8.5/executionHistory/executionHistory.bin | Bin 19663 -> 0 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .gradle/8.5/fileChanges/last-build.bin | Bin 1 -> 0 bytes .gradle/8.5/fileHashes/fileHashes.bin | Bin 18697 -> 0 bytes .gradle/8.5/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .gradle/8.5/gc.properties | 0 .gradle/8.7/checksums/checksums.lock | Bin 17 -> 0 bytes .../8.7/dependencies-accessors/gc.properties | 0 .../8.7/executionHistory/executionHistory.bin | Bin 280533 -> 0 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .gradle/8.7/expanded/expanded.lock | Bin 17 -> 0 bytes .gradle/8.7/fileChanges/last-build.bin | Bin 1 -> 0 bytes .gradle/8.7/fileHashes/fileHashes.bin | Bin 25547 -> 0 bytes .gradle/8.7/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../8.7/fileHashes/resourceHashesCache.bin | Bin 20741 -> 0 bytes .gradle/8.7/gc.properties | 0 .../buildOutputCleanup.lock | Bin 17 -> 0 bytes .gradle/buildOutputCleanup/cache.properties | 2 - .gradle/buildOutputCleanup/outputFiles.bin | Bin 19289 -> 0 bytes .gradle/file-system.probe | Bin 8 -> 0 bytes .gradle/vcs-1/gc.properties | 0 build/tmp/spotless-register-dependencies | 1 - 26 files changed, 31 insertions(+), 9 deletions(-) delete mode 100644 .gradle/8.5/checksums/checksums.lock delete mode 100644 .gradle/8.5/dependencies-accessors/dependencies-accessors.lock delete mode 100644 .gradle/8.5/dependencies-accessors/gc.properties delete mode 100644 .gradle/8.5/executionHistory/executionHistory.bin delete mode 100644 .gradle/8.5/executionHistory/executionHistory.lock delete mode 100644 .gradle/8.5/fileChanges/last-build.bin delete mode 100644 .gradle/8.5/fileHashes/fileHashes.bin delete mode 100644 .gradle/8.5/fileHashes/fileHashes.lock delete mode 100644 .gradle/8.5/gc.properties delete mode 100644 .gradle/8.7/checksums/checksums.lock delete mode 100644 .gradle/8.7/dependencies-accessors/gc.properties delete mode 100644 .gradle/8.7/executionHistory/executionHistory.bin delete mode 100644 .gradle/8.7/executionHistory/executionHistory.lock delete mode 100644 .gradle/8.7/expanded/expanded.lock delete mode 100644 .gradle/8.7/fileChanges/last-build.bin delete mode 100644 .gradle/8.7/fileHashes/fileHashes.bin delete mode 100644 .gradle/8.7/fileHashes/fileHashes.lock delete mode 100644 .gradle/8.7/fileHashes/resourceHashesCache.bin delete mode 100644 .gradle/8.7/gc.properties delete mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 .gradle/buildOutputCleanup/cache.properties delete mode 100644 .gradle/buildOutputCleanup/outputFiles.bin delete mode 100644 .gradle/file-system.probe delete mode 100644 .gradle/vcs-1/gc.properties delete mode 100644 build/tmp/spotless-register-dependencies diff --git a/.gitignore b/.gitignore index af82301b..4620c279 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,32 @@ -/out/ -.DS_Store -.idea -*.iml +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -/agentmanager/frontend/src/.nuxt/ -/agentmanager/frontend/src/node_modules/ +# User-specific stuff +.idea/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Gradle Files +.gradle +# build-Folder in root-dir +/build +# ui artifact build +/ui/build_packageClient/ + +**/.DS_Store diff --git a/.gradle/8.5/checksums/checksums.lock b/.gradle/8.5/checksums/checksums.lock deleted file mode 100644 index 2df13339566431a79415aa2dc23508f6fe9993d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 ScmZRsD&drhT*En$0SW*ig95Yw diff --git a/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock deleted file mode 100644 index 3a72896c791e16df09c1583295a697c92aee2b26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 TcmZR!le;waJ45Vr1}FdkEqDX6 diff --git a/.gradle/8.5/dependencies-accessors/gc.properties b/.gradle/8.5/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/.gradle/8.5/executionHistory/executionHistory.bin b/.gradle/8.5/executionHistory/executionHistory.bin deleted file mode 100644 index c1ac398b54ce2412ec70759a59659674004d019e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19663 zcmeI%Pe>F|90%~X>lP+O3uOo$x@cwSj=JNY!~#vrz)I9DL1KA3^W1&8&Q5RM?pDyv zKvXoNLMp^dT_U@PSR_G2CWH{u#e+rADe97!@?f+7P@#j8hluaM%zMnA-=7cf?Pco; z$#buy53+jXu1+z700bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafK;VB7@Y#*zq?*Yq zawaCbF1>8ZxmGFJuj-ffSuv(i%=& z1rJ0ej~H8#YhA!UB)q#eVQYw%l~=SzyUDuG+OC=`dvZfP*RLr*@mWvZ>9aT2rtpby z)^tcp)2db!i-{Q5n3Y*Ic8VlJ2EbBM%c;1^Q(he4hUXeL2UEAsGhx|8Sv~fnA%n?y zM0BA%Yp2C_tdzW9on_U_yo7m^-y+p-H z9%ha?$L518ndOk~JhOf5n8a-yk>AFAy|=#hEOvap{cyBDnexrN(=}v@h`q}sXI3)x zhb>IK8k)Et-+M87;N$a_e6q5zmEBQhpcV2 zJ$Y~54{j!Ec64v$S@-8nzdZ1ecP(wb>VC=e=U%GU`;plT-5)o7v&w!(?eRqb0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_Kd zzX(KSI4Ebpncluk+PbRtyGN$7!PsWkw-7DstGQ%co!}XiYvgw#9a?xVCYEz=S$pPb zqg2odk4hX|HYX1AXNB&{>2!~%KepZ8g>^#ztRTLc6QilMos@6*xYsV8$3AK&Ix(cq I2z)w;A7#9F82|tP diff --git a/.gradle/8.5/fileHashes/fileHashes.lock b/.gradle/8.5/fileHashes/fileHashes.lock deleted file mode 100644 index 88332551db4ab5652a556806752e7498b569f268..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 TcmZQJ3tFo9PB_kh0Rq?nB!B|v diff --git a/.gradle/8.5/gc.properties b/.gradle/8.5/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/.gradle/8.7/checksums/checksums.lock b/.gradle/8.7/checksums/checksums.lock deleted file mode 100644 index e8bdd909d31d4fdde3d3c0653c319530d3790285..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 TcmZQJn_7Q))z^}33{U_7J9Px= diff --git a/.gradle/8.7/dependencies-accessors/gc.properties b/.gradle/8.7/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/.gradle/8.7/executionHistory/executionHistory.bin b/.gradle/8.7/executionHistory/executionHistory.bin deleted file mode 100644 index d3f0075dae70a4f2f03f8d2d936d5faf5c2345f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 280533 zcmeD^2V4|K_j?B@DC%PGNsO_>-CjRyP*6Y=1?(E#+X4s29bTarO^mTcWADB9t}(V) zqOoI%z4vY`sIiy-o87$zXoR4_@6Y%BB6o8;v+uomZQh%i7ldJD@i)T%F#dlY{GW|7 z4C}d{`z$WtiD9Gna-TiQ-omi4gWqv*!=~?7aMZ!<&2GaP8RIb@uj)Pv-3!S>Wr&T)TMU0egE1s^Z_H ztr0B$d`O_Zz5Jq-Vw<*n&gUCMbuoK;^}^)5OP5Mo`R7;Hv$rcN&MY}~-xs5~cG3P_ z_ICSUXkW+o$^SQ>0{Ilkr$9ah@+pu{fqV+&Qy`xL`4q^fKt2WXDUeTrdkf?{2Ffxg3 z!hh}>#84(PX=P~qsv>>*dTEWF^-30#J-uA*v=}=lLc>@R&3a=DYbFzzWRp2QKxZ;p z?PDhzY%zKxH()F7V<2VKn~YThXzR%Qz9qXe%P-r zugk1<1eFuPKkf3ejGAYRFrZywnFGx+7GIIa&;E%dQbQoNmZA1%Xhvs?5jnbWUxior zVGG|0P!Q@WJe!Fbr@OsCcq)J!KAcHn4A=aQ8=Xiu>+MkKRA zdb0%^ZEt2ty&=LFY&M(Bq9Kk((5zzTBk(Q4PSkhn@}|y5-$Bu7uI~{>4q#Fk%4Q{X z1_qm))$hP3i139Sl+YU~GcSFHKo%yUDPRJMT;%ADxRqy&7NB687)Xl+n`j@}`J=$l z*l_z#X2!sf76uz_G1<%%gBS$_T&Fhx6MOtfs7G5#a|~n6)b2HiLhpcwWBvDtC2cIg z7!n;N5zLc>BB@x$BH?(KvG~g*ESNv|8$d`9Vc!2LSF>HH z-S-Qx=8Hy(?`0pltn99YZ}dkt*xv$Ffm!fRW^@vHfIL7dp*5^hqt;PM8Ka`)1WhSe zLPd}YEda~{4q8^DQOZf}+lKMyhT&1LES%X+Y76bWzgx)mrs|~n;-?=F1(LGbNUO;# z;GtG&NCm3^+5;+DR;poS3PL9*bxNI_P?0*RQl+Nk(k#)i5A{x5UVcDal1PIdJRXpH z0UH2TCk4jQF=`p$qS25vt<&k0GFGM1sCR8$(WMx@mz>oCK894T@5wX7+bW%bRB~b+s0UC)$p;fUON=iso zGNq1^>NH9Zw&2sx={X{}a8Gpvf%k*r)t zsdQ2m?XLc_9?_LeS{$z3p@8qy8xuFB{Pb|^MU(hk_K2@H1tjPxvx#%uB+ifiab!GL zFfd18JCXTP(khKiO0!BSr6aWrs{uvBN@XgBq*x`X0==VEC`n44)fmYB!WcZeT>AU? zn+1KnU;NbQ^whJ9swIgZW)EWI9Q^ZCWHAE-t<_L0sa42iaz;hUNhu>GK~xEiOhz%R z7UWf`V8KIiwc~D6=}P!h8$Gw-k2h`GDv7&VCF+5<_~wT|!`<|P5NK7TOi8LK6-iSv zxkj!cRU{!NlmNaC*ocM}w>O!jr|vb02tt?=^B- zA!9WJD<@>219d=tB{)JfLCaY!17=CeC<&d+T`jsO^vU}R_Ke;yIx?fVL~%Eu$9nOJ ze+{agJRqW0CdDdf7#Y}0Er~c>!zguH6~ihtT9Q!9bP9!525LtwmxJ%7$hC6m{Y9~b16GIam{L2U%ijUF{uE!y9#!88sYwTZeiH9t*)<1As+Fux zr;w}Epucr8pf9p93Z-00%Lt$laeIK zu&e^Om{m))GFGXSsWT4XFATt|yhD#_s%P%lr~0hKvVQZIb!#PlkbS6Q^*R&~voZmK z+X)W2qs`yhrU5-m1T@t{>`cOd>p>8(P@?5pnMSITDz#c2p@s&fT%*%z6dE<5VYJGu z2J&|qh{vL1c&DOG`~C4{p$S!fdNcmseL#n@%jZ3!j}<*NTOt)H%E4;8^U29QRhi4S*4GB^1{ zltlu|Sy<32G&*plNEx`}T0%#FX3>EM0NxAB$ie)B52IoTl99h<6tpOY|8uL?%AJqZ zZQfE5Hqfi#$@wipUgn~=MVb-?UC6L%@cv-^04^7)(a>62F4Jjclun`4QW}MlVF;N{ z=3GlaPggx?O7hu!C*)qY9w)=<5RVhDijQQEyjhrd$ILRM(IP+zT%lw@i)iFZFfpu> zQ0SC$nTBO#YPAB~Zx*0efM?~_*A-6D$<@o|YpNF+*G+omk$>8V>^e{HfRjl{G&2?y zU=o0(DOL?p3WY*O$p9#T7kpMyt5&EOHLH|KwK_StB80<%bq?SPah35q`h?E!-|LTF zm6nbxadeybP_9vyvoZXd0AeRBMG&MCJX2E1b16Z<2SP^DQgESYEd^GGv=f#yFD@&B z?Mo(pz4w}wop7PtMtSox^TlsO+4ieUC=H_^buyirMXN>zLJ#(CAUHB!Y*b&iY|oBtSgzb7UG;BPrbmh&XOD1{DItI* zEfCU4Nie{WR6MMh15onBnwrRf+;R>6_6Gkriy8D15`Q(wzgDHyNhvTM8cMCC)k-a+ zB~=t)K`9iJR;Oj<5K>Vq-!=@Ae;9_xWR}O+gb~9_tU9rz?aX1TxAYfp%idBZYfShs zoC5&T&*nP#94-PwDH)}V1k(=eLo2l; zxKtn-upEQQpkx#{StO`xm>UP&oQS%z(Sq757Z;kbKdejne&YD>#tpK&49l90?ELR| zuf4%Pj*izj_(!EvXqALkLCL`<(Li(umh_Aa42?{qBncf&$XO}NI;rk9iW}XZpSd1} z`&4e$vCFi;x}C=!$*v=tH5zVggI=!F8)-ZAN|}PDbQA^Q9tPsju=s^IAw;X>az>$6 zfk_7=FI8*ZNPzmg!98kO=ycJY#kEvk)Ov zz$!;aDp3qqrGSMF1#3oF&=WMMRXL*p5dekc?$0{L@pp4;8(g^CAo^&9;0G5g`RwoU z^D*(Y4}b$V6?S)=qP23R1}PgA4WS2ulu@8?q^wFy>9nL=3bs{BF*=1i_#E%MLH>=d zPWQFM@-iE~xIf+Y)g!t1O!gpu>+%QKDFO26_O%+JRFNQ_VC!i$#C~8IK>&0lEQIBt zTjUI_)MyBZowzQ2gdyCPJ=WmYD|?RZ`^TJ>$pvE1^=>83p}*>kSOa%*@g4hW0vr|2 zUqxXWnM5nosQ^}$QmZ57N(#JdMgfZj1>{(CTDc1JJ;N~IYrSO@aJ69+-du@)8lPVJ zMDup@yRF;5rB>@S@rCS>8ZmmD&SbF!*p~#{H-Gy#d4LkqEBtzg(!es9pfx1K)j+o> zv;-{9XoZ|mgU3KoB(0QFBp@bn8v>wp48g-ZpjXPqaYe2#+FdPJy!dJjv3O_pV0Kd~ z5-tsv04q1(3N=L17_CMQq$CMeMkwSYcrPjlL}|gjQfajcosQ5FV0Yxse!vRQj|Z)O zOvBR~EB@5*QRTGVV_W=kTzoW}pv4AsH*vJofy)BEC}VWtO^vf#I-ZPhdT4{ow_!Ci)O)Hw)C22cQ@P*DI-3R z1NhuG4B0*SILaWbQ%EoaG8H&)u-xka zboP_+Wm3*2%rlIcwrJ9y*#(h$3w!eMFT|S)g3>Xdiquj9G(V+O$rKa;X(Px#N`WoG zhhQPgrKX*{$;ZANjCpzfj^Zb$Q`f7_?syw77Cc0}BL`IESdW`)VU^EKu|_JBL7q(w zaUq2c!il7ufG`Uzqm(p+$#oPYhaknGR!bZ`-RAelb!kIaAF6RX=DxZ6@Y?H#WLLKT zL1g%F1Sc(!fKXC0EhKOu<3OokZXv-&NlCR@Et9FVa!RFAOToSh(gHo*1m#QrMd`E4 z{q${e<-0w<+Tq_soR<>(c)|ETZL_|;{sr?}vl;^?y=nQWrT1~uzGsVH4_>fXza+L< zWUn&gKiT9y9<<|dr;Slpp7jYE(QL-&6N5hK)INTFpZVvqo}0~Y#IeD;v&{D$$2iJkC)yzzEZDS zJ-1a@wPJYZ&3()NZ>00_{r>$*)+^>!ZN{$Fv#FvX!}EADAFn>CxAvBi?3KJ)WGeDl6Y74rOx6`xKbA7>h!YSr`Q{NaNO>^%}$*{4okP2=Nr z!gp0Ue5UJ{Ea4whUhzzoXOsAN(OIkMdZt$u+Kn}=tN!ZcZ`st*?}!ZLY6>40{nhFc zQ?u59^UHKTqw2+aei8|M{Cw2Cxn-uDTXiRt+M_@CXUk6@eUGngbaCY$k0$^8{mEm? z*Dh=v@(CvI@pJw?J0A79-(?SZmRYltZSbk4?(wk?o{6f*_GEfWM;{JVZs?rDK&Uf~ z;|O6on?6M19b<76-@(3l zFkz4X5Z12l)H81`zw!xa+oI{uW&i7RJ#JY3V(hcAw+9e^4R1TG=Lq#DnyklH^xS)D z;g$zyE`&|LdgJNn;r|P%dVFfhZi#=M?%|obJUsozxT3>9r9?fB4=g45Zmd`OgnILq zgkKrkK5wV#ajTEl6Pe#(#jS3sO9O_My_3xamRlkUk_i8}Yvn^E>G5`B68cSOFeai{ z#Iv5mZWL>uO;Et|qrB?ec4P&(H4;1cNvFA^#qH^ew ze)QBlUVnl5%-L~A>b7_|bAjHtw|1US%;WnG9=NunWx7w-QNzZUE4cb`UP;U2QzjXT z4(;5q!#BQLJ8oQEWo_O}%H!!?^LHI7-dnfZ#1LgbgW0^6j>oZCm*>XyI54~9`tmoZ*MqNQ6TlCi zjK?pBq%_ifJE7~Vq+>I0^{g-_kEG&pxmju}--D?BuK%zIrAW0t9T7{Wty+1+HxF z+8^$jF(5@}GUqxauQ<4O@&jG?%Nu&hkHs0dwDdBrc+od+V~Xs4IYYEx^j`SA?;DZsKT#PeP-BUhx6NpH$g3w93}d zh3vJcc0)u*-fQBr`5sqS@*|0i%iY^W*Po?U#i9Qb32 zqzm0M!VzROwICA{8MAwT{iZ8)H$WCCKrwd zv;~A;cB@&loA__hiRs^e{q1*)`iatVHnmxg@0wHn_~@D$*x~GC=18Wyjj`BmOZA46 zTW@Uos&mIp9jfm-F!m48v44U1JKl8iKb`SRfLRjZEC3@+zOKTftFjkQzL9AE>Ri9c zN~7p_ZqmTzRsVscqch)nIDYG#|0upiz@50cdcbb+gt}wKKaFVHWyiwoOZvOt@cJVe zUnYfOxU0V;fr)zVmM{Uq)=a_c7y7-dRrklGirZDTFA@FrJ~sF*!?~_J9Z(Y5iX~+tLa&{UjA^YaM+aHUH2WU@BKxpXwQ2w?t6_c+WPNj4ma%S_Grb9 z*W((eHS$QE)cOG)-7!5Z?sVa`JL=pp)oZ@l<~fE2T?9wsk=p?mzJdQ?GN)o`IeNFw zrZ>?1;S4wt$+_Lg0mu4tGmZ^%`0dyL-u=dgxcc8%f9@OBpRhx+Kiv0{0+3rJQ z1HiVz1)!qwnZ0NMsH@ukr6g-xKb=;3n!UIi4{!(MW%n_%Ko~s!V7u>52tvJIC`NRtw z?6maUXTdKNO>(Lk_+TdZEoD7B}5~fUF=h$88^82 z_<&x!qNWrna&O^_s{FIRml92MKI?Rb1i*`mDy}!H@QB)R}gJ%lF1lx_PFX>+A|<~=33diamhPU zdY;@d)Z9IC>BR9f9*>vJE+v}!mi~5tR(ZDXv!dPjFQ2Kc-#tFpT+$%4Zgn7HwvYl#MToP88?iHxtvhe^k{cU9 z&nq1dvW1VgD2fdf3HKXzoAe!1~CgBS+kq4)|j2p(`A*4po8Sg6bsi zs`ArFkJPDUD=t(&^n#{dFBg1wI;LWWv8!)QJ{&l7J2uFn&_u%=+FuyD!HrT+<}{gn z{o5g)Rfh-vK4&Ed{Gj5ZDR0rNLcdb+pN~CVxg_4CH~#%m(}3@vav=4s1@G@XqUR6I zyVy1lKeGMBl78w<(Dc~`%xm?|QNM4iRPLc};>#px`t8i}m>tVctn#X~SX*+-g;vm% ze*MkdZ_1ZiS@t{s?a!b5+6|h5dc3@|8{6wQ{fkn@A4Yt=6`Jl>VJ?OgyGlk}7jTp6ePVR2cLNG8JhX z#hxO#v3f&x?%fn_$I0rYXTHA{a{TP+fMdIS z{`fa>%l|HjcjM-Y!h>JU@Af)+%Zj9C5ncYrf_SSR#5eqI<8MQ&7O{;>nD(;f|9TK_ zThQE&4%IEP!3;ONEo5WV-M&mgX@Jf_v&W2c5TDrCwKBA zTM*tbOB`z@_dr=5i){TBcDzXcvU5CT>%>pzM(EGvxyV*h(b|C>W1AL#+99&0vc>!= z|09tta_ZztdrueoJ95nSmRqa+es^R4(GXtPT`ogp>*V8ZFDf^<)4qI_%i9L68LZmG zJ$AS}tin$8Sv>ad$#+u@;ilrV+XU@_rkUq`XGMIuu;TCKu>IGl0fn-RH5Hq`eP#O^ zHMM(hnD)*W4X)6e8#1S{raj>YjkOil1O8|qx$Kau_>Cv;S% zuQlkK&i%EGqE4+kDT{0|jC(_$qG-5BTgtE7?b~p8jlJO)4iB#hO`Qkrzq)?^q;H5Z zYdpvLEIN^`(9wQw=zk6!HQ%(l^DieKpL{TMXZ4QSso|qCy<{t#74e^ri)1fyB1JA z-uTU}kj^Knem@JE(jtu;Unj3hF8GSN>34We7iijDdU;8CY}+|>^YzQ_-l_6Ue&`7H z*$6Q+M_}ud2^}5sZ(ccV@{@`yw)a*Bbvr%m_5h@d|K7}njtq~X@|lMX!$b!3-z0^a zvc{&w#ZyYwsx%}RQw`loT4A+y?~aeWIr+~mEvAPrd)=$s_EQqyf|**dEh4m)pVqT1 zDotiDQwCvRD-?dQ1Vl11dUU~N5R(Wyo1k!yp0Pwj9V?g8IT?$Ri9W=b{Q-zXOJPhj z*k;sQ`3R%vX!e46T#g6@@$A_7n+5FrX&3Y`eD%(5cjNt)yT6*!YTwQdzyET5+j)rG ziBQ~5lxl{JWT^I2RU!Zt$7#iw6ZG7jvKDNz`)@!txGlL!ENQkdR-x9B>myJd$Y7(H z;DkhLDyn!S5)F0^AUcsfs}i0pmaGO>lD#2_dFoB zJ4dhPGk{L4QTi`4URWmFUvAp>WV{Snh>Vb;>Gp`NzxjBb8WTG``N`+&6I@FVkUmc+ z^X3_g{+pDLY=DA;@oZ{REbJ~+r`l3=1}Gpw_R!;l^$qG)PWLl!E_>j3-``(^cw}n9 z)}dXgLfJukrL_RA=pI*pP}mn5X`f3MsmoXbO!P0t%2>Os-~=Zh22fCEDhie5vg(r< z(P)=n(7d4ys~$g0bZym}U1egb>rXa5tI1fRfL2x}Ce?eM3jrXM&52Y%P^RYwhPG@H z-XbqaySVi1Y?!oSCJ zaYTteh606|6p%>BJn(G~zMQ^57wg@raGikp;U`OphQsSzAIUvca(dQhWnakpN+&PE z0&A~Keez$cl8h7i%gaGvCXR_QL~+s2E@Z+ok%@nHCdz<+xuy84! zDQL=wDN;P1Hc&i0x5IxM(SMWT6MLwPJrdN$9!xSFD^tcB<4L_rm26?~3tPf_6dPFU z`Zryg445~eae*nKOij5`IW)iEDL4T61yAz}p2Bv*cNWS;>Eib)pJ|Jz2VSa*$AD=I zf2~odmSOsV`17Rb*Z0|8ooS$p9~~E$`BU) zVPj)%w}8mg`{0`07rEZS@o^NC3xpXD2EA?9tq0B=d@-@=t9fsdnk_lJHM!Jb2vud1 z8t?nAoTb$Njrw{ALF|fbK=sy(cBbw7xys{vo!esTb)p^r7XAN9r@aH&3_GDcaQzHx zx^e9IU#h>JRQhz25XpP$wD*5S&i0Vx>Rh9Z6ipnvE6^qiL7_Jrt-I9m_k-o;y%-x6 z)umaQXkX5z{4Foo>QXG~KazV6vVXBX-2?BQ5M6L&zbcPvZtp!=qx`C|=<&ax(Z7J? z@_%Vhc*XV(V%j-M*V?y??|{qa4RN^#(8HY;SY&K z9Ml{YkRT{6)bf*R3XLao&lgXVib;I z$!-zX2_fwwqZyF&rrsW&EWO8M#G){Ca>7OZ}2 zbpoLpq}E=zQc0^cGAYeMsX84L2Zb`ravhYHRY4gu77Ct%-qAwgQ%dbxxY9m`Tjk12 ze;dUjE@B=IX3r!Z%X9e{M1n{wuqGHJ7#!2N~e>o%ygAcvxmQVGF8 zH9G=!tH}t8gu3xshSJa!MZh0INywoPBSomCT6a~Sc{EpDS)}WZ$MS16{<5~(duPYt zx^Kj;LB-re$ZoBmUbI}JR%xMVs0OOFDb|iOcy^2ZW}#j(&q9_HKx;J=6bx6$WO7DDLJ2{r)JH?1 zbB#XLZ6H_LpW5iT4S&38+g3^3)hbaByv3hRsTL1Bv{2&6rImvU z+?w)eaMfOTve4 zg_@HJDCkb8WjcjID+A@PhVqj##vz|VZ&zLa`gEVklOnI`6UMhbyYeP(6lc?NzEvf1 z7fchV_@-1ss~K8_iilE51r+L3l2SRXRYS2^DXmhmGBvO z#iH?Vs#L%6*VCo55;u51Ix;r9_RwC3Iwiq??lPbq^;ql;Ts0O2Dkv>cL9u%fGp$Y| zSLjr787zRLN}ZZeCBaOvm_x`>KLg`3l++>3<)li0&07^b_OeqI`a@lOxO@O z;%U(0+Vh6po7}KPqIhrieL6Ckgk8P{y){+h@=*a*!wCtaROwVw@DzX%72qjA1zjEd zLqVl_9hCXiLOo&)33Y#6dcx#FPaeGRHGRg{|GE1^+0ql&Jbj<875|=n@M6gX3ih^I zC5{hJC|UxrNGYjSrJDl);v&R9dLn%}SvzG87&sXojX7y-?L$9wt1 z-6vE~@{~@5%UoEKIYD@#{VTeIB>^t>L$;P9Hq1PsR6spERu8@f+1BZ2qICyAW|6x7y7v~nJZ&p1*}8lf%l%pY&d0D zJF8I_aSqA{B*1mWdPsx{e}S1@SQ^1ffO!I21gf2|LGV5<)?+;OLQHR!LGRm9vZ-v^{(vWF(R= zEv=$6fl1X#wNf<%p7_Frj{ab$P=DUp z8?`KQ*4FgTpLMU9{&Zq3sZD%8`^$ad(o3r;0g8zPFbO(_mhfNvxi9{ZV6?zug|SLt ztwsbu>;bax3W!}pYy$cFDp-nWAwB`235K9yVIqf32DDVG)4pXKe_Asd&US`7=_ov&wdL$R;>0*5zxHb6o%ARBU{xN4| za)H=$y<3TM=nH)Ga(y1S_^0vdrB5_(H^1Au{ab3aPRq%PJ5FbUn>K{YT_xN%fBQE% zEc4_6P2rk!g9HK@;N8*?&{EL~O0LinECgp@WvEstVP6SJE9Df#3SgOt2DA?WQ9{QM zJYx^&m9lYMk?V_gR|^&|zFI>pcCEAW9_vLnrNUpjo&cx$VKJ+Q&;SDoT(yFR$cqdj zmL$YfRghlPf)lRNY84QbgD4opxt#rADGU90(CWuDJiW2vPYoYcPTM`U#V^Nm$_XSC zvk{Un_E@&F$>9~sB@ktWH7gCdA{r9t0HTJ0y$pm>sg%iNYPAaDMmi1T?3})(V_-B2 z(s}j(?C(4L6`f5Bn)GQ`{l_Io0xx8Dh2t8&hOjDE4$R{38p9?a#x8?=Kf&lA{)V(O zfX+Z%8{$T=@j@vjXvm2vSxJWO0Hq^Vuxh}oYIPUZ|HC#(U)Q(a&T-j|wjd~Vsa8g3 zqv=!$`sB}lgn*eMfKW*o$ZV1T4k0IW2tWeT`M{i%nu5@Q7P53GPAgTbUHYP68|urm zKysZrr>EBqoVM#`%*bX5>aMNa!p7uxKB8e2`TBJZ!LaGru5Wu}M6)jg1EM$gA6{KicCGasJ z)2Jy3Yr&&5LqQe>7{X;J|Ez}MNskwiZED;1M&hnzN0n6<^t~$1W^KZE1d`$?gAhj| z!4N~Pg@n8^NogUdBv*og25Hm4Mg-s-79zI{CABZnp`&YN*&}rJlksIz&L+$=jG4A* z(w}12L!IxVyJ7>nn>b~QinU2V&7!~l+=gU5#l_=P5|X9VkdakMX^mV%%5^kki!`iE z3OuYqu};X@>f{Pmo3T3=loQz4qvpWlmv#mR8@ddelYZ&oL~&lP>*;}?C|O!4)_B?fM`WDTCyKLz-&F*fvA5upA*yUD}_n3Rco~C3OB;pbv zkCq_O2EbI4GO3bc5#!N1S_6Sr*l`MhcPVV)q~xrUR;tuWg^ENS{GkI6N#ofKi1GDX zj_g@5cJ`i!+A}#xwwd5UEfgaKa=Cm&<^;A#!Tt&o5^i!*En`*4$g{Lct%j`)3@cM= zRg@g42zJ$_C-5Hhc6%O^J5pIxjeCBK? z=upUKkuo{KfQTp*v`j4{bP!*I{VOQ_tD*@A5<}8W1#xX4oqKnrK<%lO&nEm9-&EFf zW~j18vt}i(z0R)2`QX)1J@Cu(cN9N4ow{CacE{UzvEU)%JW>zU1OIVd+R)X9YTS;w zZ|*+4_WB{&)rCkNK0;*y5By92Md`E4{q${e<-0w<+Tq_s9FnV5e(-a3zAI+yZdtfJ zQPaHrGIC+=-5{3fCZ132v;}V3_iXX&!3!4am&7)U>{Vv`CwtZc5883K)5fSP&-#Ro zXg1^Xi9w(ANele?KJ(9IJvW=*h+~6wX%mir$Ri3|7LVV2tIoZ17I;r=#-i~>-VE-t z`>XbU^y;GikSK6Yu&{qP_fA>hW!4ARYwKHk)7Lkqj(T44+1XFyj0Nt$rs(h$C5%P9 z7f!w%zc4K5(>!5;m-@P1V#HZ(*STrwH*YjA$7XYv=sT7h5E@KKqJ$NPMxj-)8cIq? zRWhXx?Xf_+E+LoApj}Jg-K)UiBS_Z1M~-JEMb6@j7w$luPTlNEUNNhOaD`srSWulJry?RIA0%}@JW1s>^l=JUs2 zcNt8cpS=0YP3H&a{ZkeAjFS!L#~1l?=;|__e|yDOsrxW{9Fv7g`S@ol@Ug{`qdxQV zKYa7PNEPz@ixr>Fi3*%)bgEU)m-B}YF0l7VWM!W^dG$O6UMGB4mBVMce#sL4G36D{ zRC)F^1zvR4YPz23RfTqA4eP4Edih&6t>jykAeRLc6ksl#rNBjhwYtRAtTo{LGM&$; zda<6aH&48Wm3%9lE=M37`a0K6QsC#K?#(ST<=m<}q0}Dz!9QDm0_Q03wT&*W{NvH& zpT9qOZ28)SjYB@cQxy0)|DGL>`rPlbhdj%y*~vD@V`nJv@QQa5SFc&B2urS}elxKZ zl}+1c&pO!;J2(Z^Ncs$v+1+^1F7v~{?{ia@Vi~-mP>s#_RYh@^kZjl^nH-$ z&r9If3;!jNkEE~fxx8kgc2e6t*|eL8vLEua1peb~a@ubC_L$GRJWkmT>Vl z1tRz?AMC6IZu;@J+SiHp+sj-@*!1ne2A+BFqy)aCVaJIR>U3P_vF7&WxRT9(%)Mw+ zE}fIWHw?Jmf~Czzs-}UByfGnXX7Sse6@E&joD=2oQGp_pzhxiwfi7v zB=7<|vBO=eufAVe>b<76-@(4Q$D6r!LIVFGtX^{E}W0RyA<3v*}HJM*Qy7%q6$tuaN$!u9f6O1@Jv)awkOk5I{I*^azp1F?uT?9 zgm5fBoK22haBw(Uqk}LwDT7mnS^{=tz;{AU!p%PD{0yyw`+Z**>mxrg{7*}-or*tL)#|M^@d^grB zeL}r?OTw>=ZJ)PKMBr8*uO~9U!-`woQkMn{Eqf=MfpNDzzp%@Qiicf^0W@hPt*MD{ zB-!OK0BZ1$qvJK8)wvS|YL!l*RT5g*v#f$E!?aQb9D-+La4cJ5kWQo{PSwN7kT;|1b(Gf z`z{S*wCfKP?pm?uPphJGX!Sq(DG0p&0`-}*y>V~t9IT>0?imPt-@yad zcC<|Q={joI_;LkTKh7&BAn+-Z3`K`_ZrI@)->n@tuCB5+hdkgs?BMj-+)gea-3<56 zm(Sw-hW%Pa!*)p4}mydD!0sq6)v-DS*YK_{a+%ML-^V+a%#!lXL zvH-=;a^vg+o~-fz?byRVE(iWp`}ywq<>%+&lMi??c2kinBVUhsb|bXmS4AJU{B+Mf z;76V(ZrJ!k?<-!b_N+eL?)Hy)?bHK~&AL1{uE&AdCD)g~LA@S)C2yX2z|+0v?>bVv zw{EwMY1QkpwQpWO@qizmy65;M@v;LahTN+fz4VK|If!O_xbqJ9gNqec4ojQVnTwgdaJebM!!Zw)F_yV9i`B*$D1IJkVwg|iNL@Q*9|mVe%VNJw%} zXyMCoLvoO4a5RDk<9y#TJ%gs_-`5r{|C(ow`_Fvk$MmBXH$|rn;GY)v4Z58nseFsc! z)nVacUAw<8;O#DzHGB4}ar=6eR^QiX9C>KEi7#sD5<3&Bdz;Lth&DFK{s6ix*aG#j z6Pp;|mMC{oYr-*<_&x||8i zAsz514Z4rn70evHR(S5gU|Zw9cy!0~u(;EO*Y2ou!&I;NW}D|2_nP$#Wi^>ov0OXV zf~ExP&lN+#2I-BKM0kPTiVcWi;7UOn8^Rq#O(2c%*NhEzP6_u}m=n=7m1UyS^)Lw(pq^K%=Wi2MuC0H($Yj%fy8M1< z@-G&$jNW!Xu*9bB8y>X8JfSIYtmV%5ndZqc&6=o}78=zGn&jWsO4RS&Td(mit@kf% zcq&Evhzm=m2=j(qJ$68q$NHyA<@t^h=HIVVk!K7ScjX}pSC{KVcAYoS3InwDEEf)`iU`8 zCOFw0p35YvNDO5?$~#jD8-D` z%lf4)P2pq$F zQKI^Lc6aYp>pgnG3*FiE-w_^I?JcNs3qsQMQxY)@@GI{Wr9iWpOtnNBzC(9=i7LLCt(T~8aQ?=7>*T8V$7&6JL1lQDT!dZfk`T7ji>z`PGJMPR0LH=iqgjzNh=hD zv0`7n1;D1Rj{%f;0-3~s8w;4iRY;tBoYAInR_DvCVR0Ky07%TqOd*RM7%-EyI=v4# zRRMlprugTbeutgDa9@)PmfZ6X)e8%4Dl(AMt{~M|K_J}QbZo}XT{;ISmVDnVNsM`f zVV)L!52h358B1DXt(dq|<8aK=2-7D_35T4AAq^xRep~#o%+mti(HHCj{`r;tC6dyr zD=(hC^!cx~4vuKlx@NNHsU@X4${+Ust;_P0PpnuOFko<}n%N)OdQ|5{xSNSH)Wvz) z1#YsT*Oh{Apa1!AN8Ez)+rJ9EQ0VeX@Ab_mJ~{3m`#?%7qc*J?N|s%0#i}r7v&q~w z(QGm@U7h|ls*xo_@fBcK*TIYXI0v86{cgCW#i3I_e%HM8rYVou^3T`Q3S6>`xZU^s zsb{J^)c{d(s`y0JpbUs>S-X@I%2bKHcMwLAI^TBN&n_0p8G z9mQ4eo~YCBk3!oH*S{ZD-M5}P$cmMO>oj0QIhT0xk28bqcSpR%1?O-{afha-G^#dW zY@I>J!|$ePW)7Zao_Vm}-6n%mO5Ch*a3@%zIExuB-i8?x?kRVH4D%l69BP$$y86hM zh30&}>CL%^#f}wkG}+gGZI?p(5BiQ+XMNacf)#6M087FcQ8ageMH^HI9;qac$p2&)EDv_tJaLW;*GEd~Xndx4ar+W&R zH8Yc7N@Cg=ZQywdn$C(9XciICEIb%K2oGjqd9LJJ9{Vx~(K;aC^5k2de9Mz>d0fp! zzU4s|?zvigr{yW0*(|-E;W=CL|KE~Fm9_iL86JM26X=MqTC!U6h{CaAX#b$gW0|8@ z8RJVK;0i;0X?;+?F=N-RQE>5?WM2#uV~xH>4yz~v);7^FDun$EL2&J$&0vj&ur1~t zrPsmgJ%)haxy5-ih~2;f*ud}$qOwuR@M~MB+N7sQLxfomH@0!XE-U60BIAbe2{uyZ z)I@fJZ`&*0!Xr8Q;~)%+P?H6(BCs%B)X-0#Hc|kAZJ<28F%sfER9Kj5nH+ zjnT2-E@HkdOb{tI8TC{I4Y=D$DFc@r0nnC&*dPFf-YSq>zyz)S5VZ;h8*K?3%!NV; zjgcpk6Z1g8+)jnS#xN?6xG-bwzyocnXACqz;%BE7N8qL~0pJ}wpmUI+KwFc+hL*Wy zLZLhZ7r10-ehfQ?-aM5fSimRLNHZw_loczA@M)8n$N(j5Mu6Nel0iYGHbyQS%IXm| zrJ`&)3m5R=ItrwMs9+$F3OIqg*%RRiO@NIEuq*5W2ebqM6Y@BMe+zCl#4nMoU?T8u zONN_(0U^=~6opB#!z*fv26ub9R0ck`Kk64s1M}(uSaXD#3m?JA&4G`bGB6GBxg~HW zR8NBe1=u5*7>J}|I1fBS2f?iCjTG=(LBO>&tPWvY!jj^bS z7DgzH=od}G*c_pR;T^~e0#$~SwO|920C2jAS`luXmMxo^O}0dsZ!yKu8;NEUNIXD} zlurghTmoL+goK17-GZ^knhIP!XW(!}F-Y2H%T>8k2yyw37n? zUV$ti=7@rr2n_{)3)tn*yOqhpi3*I>)Xa3ZL98E1T~i3p1gC&*DGd1|U>R#F%olKe zc(jwZJVRtrAScBfaS;0?p&vjOAy|Q55Ve3KEtY5?Cz_oEISk~SN3IFR@or(WSP|MV zn*zzjZ9-upTbitBnz%`~VxGL*7IkMOc#LPdR-_pS4{&ps+aW4uAKSsuMN!loaB(#F zxpO`zG6fr(Oa@*b*~Q%#QQSTn!UM@yggM*<$`EFlH9+*+{#M_n-CcE-FH)+A^?9{a zCdg!sLWxk6z;TiTX2>@@I--ROodZ$a6)V;Z-DAnnjU94RJc=RBRIIzC`XMcpffWka zAu7-Ks2ODKu5S~CzurX2AI^cPK=Q9GTE#`q!$>dC6mmDYLPse%$zjY z=Y!M!yds7<3?UGMpg-sx&7y-_GBF@PNlXN6PQ?6xnu3;cOpj-@9w|Ljne{?(=QIWl$^vAN%lf%yE)eR3d{!f@377g!r@pn=I%SKYkYM zCZ03H#q1K~pa{+n8pJ?BE`1^-?tw)d@3AA#af8sst{95ihq6CZ05In)v0oNusSxK; z2qvQ_$AeKYRTkb}lx#v8k?|xA$U;V#gHfmjX$_#DgIYzQ@?0Phwil-K#)g%JYGi>L=OcqFdse+*|B2f-I<`N*_6=6Xc4S= zfrM7UkrrS|KY?gYe&S{|RmTJx^eAtL)RqaD56mB@0YdaKpr1={>c6oGRDz3_Rgh+4 zAbAL)?ZJ5&9`Q&A!oTpMGJ(m~Ktp0IiHrcJjEymoHUqGs4<8%nxu!`IZiYCNIM2&7 z7$V6akEk0&TFothI7oBOpu_C?GK8Py!Zg0wb0WvLe7Ijr-+lh%?vt_ z=_WQaufK~5g6?4VLV*UTG;4_>S?FEb%{;pJe&YC+_IYx!Upc4BiZEyJ4T~c`E7fjZ zXx_Q3_7=^Ysdr(HGwnEfb0&wwtAL%t<=x~X&`3LB0V@noadTM!F)FfkB{-KRqhjS$ zh0`PVZW?TVr8pk^0`L`FWDsGP=#+hQxy(QN(W1IOYcWX5sg8h*jV` zO{dNQo#LRV6)VT_Qxv#;U=@s@4qRkFjF?@F_`XCNkQlQ=cm>C)QihQ;5}}a537j|- zbogODflZp&sZpMLeTd1d?*WXQVWm?}=$~0$khS6{ipes-;EJ8#Zia%#U<6f1JI!9W z4IED1dGrc2!U`N@1^K)P173s!%`i=%cFA~#jy6Ry7FfA~9j|a5H#f!jpY6;H_Q2ao8}BBd3y_`$KsX%> zbhHo0tqhzt7Z?+W$_-hP2N4`-x4A43F8IitI)YsYF!fFkCX<-pcI(cmN+2pQoZx=h zLGS^Lc>2I^0Gf?+lK7Q_j|)cSo$`};CU7^cz#-u0U^AzUz@3XA`|FE<&TC>an9Na$ zByw9gznX&_4Qos(lCmIZF zjv2~UkRVK0u?*sm!YP@oD_A>+GvUCffc@3p{FVk}nG3`t!wEh?#&>vY#M`H-V16$Ee*JqM6pQeo++R&GqLI?%%MvY@>!ILWDj8t&aSkis%V}C2C zMUJyeXS5pj2Rt}0qI@P9b1}%?PTvZ5zSTZJK?e(FB?opw6|oalSos!p@kBWI?am`7 zFF%pd_+F8Fh2uuR3lHA|<^}}v6_l1u6k$a~vEj)r^ zlJD4+!0pBW;Lu~R1pspRZ`pg~h8~L0RQ>d~S*f$8F1LqPPX&bQDaL4FLbz?PTx>Ve z1`($Orewj#z03bUAw=!_Tp5N35hR5e}C#9blo3 zTEpQp+KuHyvOs>+3OdGRrxWikz($cmI)*iwxqfJiLSfiC0&|PPnhe@z5C4LODCe>R z%Z$QY65%Hpz|Rt|PYj1GT^4Q$1wke>jxTIoNrksU+#$k@Twh^uAJiCaqRCXQ0d3gA z5)ogN;bPw0_DAjw0m2)a#vF||hKh^8F3nKPi#3ojuw$oSA`h8!3?D02?VB24Q_)_q zo)D4-+ut)0qP%*lCxwm$^h_|(u-K{jIk0OnNv+RoG^zPH)P_=gUQ1FaF$Dl@y6!O(D4(0VN8fX|ng>`Eo(ldD6F0$D z1gQ9hl@N%=x5b!ErX;Xcer~PkNzni+X@+oMktaFY6^#x~BtYDO--^xc)R#Cm>Pz^2 z`VyEsqgB|ZF9}RUQC-e6@+K%;b0J;Cc_*M@7Yrsx3&A2@)`%#+KOErc`q8Gpmor4vgPANHpPX&$?bP@E-v$ zz)Uo(v1Y;Qsa`Rw-Mx!-YJkX^)swF0qCX__ zI?l&Pni)(w(%?Af5&}mQAgm*RePFL`M4glABjt0;KD-#WY?Ns7J05@ikyyeBdtjnI z80~l!psi#cO1J-P?fG4Wldqoc_Gw{(?&` zm@236f_v!V$lw|Mn7rbx;2E8j|A2Jw_D%4NuE~Gvg5ViFkdH;M_x3vQj7~?MaxUhdk^q;2FII{M420qYvHI*1a^~LFsZn)+wUlkQjG6ynir9-Wh**-+tOk zV*d~ZVI8b5L}svlDExu-Lvfz>zDA)x=hS2UIj`Q;amR3R$@sYoJ0MqC>> zZO3`IDB)%SxDUWL;8BXU&fO~qHvYP@W%m)aps8~A;$OUeRb$_&`ZIf__D?A2gzCygV^;3<<+$s(pRQ=e>))yxXT!bL;Lyy|*Lq_lw_pbHv+& zIj~QtVxN>+qwkOEqZw1J{lTi&c5{QBEex=Dr!Vmc4wikE93H-3Jg$3nJ1sLVFfsg9 z*+R=kczoAOm+;j$qS-AY`*JwG_I&&L8N;Ku?(FSfr}e(MuTomHj}Q-f!M%8jAJ!KF zJz0f5F#PAW4gEIMYZbWVLBn(=%=rCHuFJiG0BiEB0^SdU*1sOr;g?%?J1sZ|P0g?0 zs&;ACn`>!`4Qtu4TC9Bkh*aB9n) zpF`8UI~S@9Y)nzZ0)Ab;d;Cg-#_>A-i;i4u8Tmu#xWW;wcD%v*q1b#jrR{B(mb`m` z0Ri73t;Juqy4>gFF{`ka!r`yl*r;^4)b%*`74u+v|4nsw5YzSFh)Aa9x4WuL996ZUwI?_OK2 z;C80ZF=4w8a(G<#w`c)+;PIy>?Dp)7bxfZ{%61>5*Y;Zxr7kzm`Q`5onj7fs`%MpY zhO`vcF>>S`gs3kt`#k`|V*k{HYJ9L13` zNs|=M3L*|wswGUMNQ!{!?IOzZY%=}$Ir`Cbf&m?WT z4{|Agk?qaTSAIHw-t3N@hGYshrR&7)KFE~LUADI=bFQ>shC&mYH|zB}wY)$t4&snh z7XcS9;C`g?0&2Pk3ybqKO<**|@*F`49Lh>0X=O-}ka(7$B~g|ni6Zc1dT}R>@IlO9 zUTPkAwPTmTg`X1TbB66VrTgUVKFDuvFH+wPTO812-B;~ewwU^@Nw2R=!QoU~V`Rwg zB!vO5Yf=4aAgh`L*yf6Hc=%$@3VC5e!GT+d+B| zN0JmDge+7hcl5E~c{SfmU9o8V53wdM^OYu-=vDFgu?YYnWu`5hEHIRa)2L`AtQ-Lq z>}B36<0vEGR+N%NsBX^_q|90ZLwfMwL*%DU(Ix<(&|D)9OE(TjYxb+SFxKyfQa>Di za@v&Mvo!ApE`g>d9_G6b8@IkKMcwJ}a(mb!Oc08QB&+jq2O?pjo z3TS3>5c+Ap5RiaJErCFCB!x*5O z`IbHKL1N>68~P}Ju(>Z;ovcAW`k3_QwiFyHWd(bRVM;;;H44PiPz-D6`U3gU%$P-j zf_xHBP?$`U7()vL1OMS1fnp3PQC3_)ao|=ckETj$H7yUC58^X&M47)HWoZ@heg2YP zb|&LY$GtyFkd^{sQz8|2X1hi4#fC3!)h{@tA0sRb4JV2RSa_VoF{tiE(FBT0EXv@l zm19W^s)$b#QMaCh!t4>kNwFC@N4iP6`+cEGAE(z_>wKG+7WBMs#y;ijr5y zx;1G0VttE+9jC5e)%n@$*ZB}{dCX6g6CVf+eqdh5Jb{C$;sXn#FV1H*wK((v_CA$~ zm2}Ck5%%24#gF8#rB`)JH(7Du>o4|yK<^Ntu4U3!Gkqhgf7ugxRxRT>gRte~~a&wptas zpIkTU4T~zY00}w#V(FXcMN4hVdNyd)FaPA{A8`V}&qi>N0l+Z}hPEnd7YT<`iL)|l zm2eUyDL9xs2-_TwQZyqFuDi-(Eu3BHW>Rm@iOySEUw`pa zevnEE;+rfEaAi!k$`Z@)B4vfOrf3#NDH&xjg{>edR>0vfTLsZ`vW`ytv`*B6TQSl; zEnC2~3^6}u7`9;b?j~N^!`x7Z0=of0TtSWzonM`BgP@=+vq%vj^BV1%9uLDfW)*GP$W@SQ6vafCQ-=U3UUz65+aXNz$5dND6>#^nZfv^dr_8J zNpGvr$eY>zwh-gQn+L9~o61gWcOZ=sM9HlaWOpJ6L4m>x2c{0J&xFibLHC7&eHyfk zFcqGXMS+G#qBQ}6Xr1U0Wc^Pj_N!M#J6C?SaQgSvvl6K&$o?+0hB{78kY%2tL|Vdm zLBbe9CICp>O5(W0una{JERAC*1wkV^-x3&XIX> z+}(i1jq5%Acwj?XX~it&fL;bOmq^P5%g`c$G7?NdkWiW;NLB(JjmTg$4g?)f5`KqG z%{`60wW@};xX5;!f9T=uTTKeeskrA8tiE$x;CO?22@chUm1JqS8Bq+Saf(+2UwA;th7QEu@ z7GAKVkYh>!VPa{*#W`~k;USuLB;>4cAO;*smcj)JrNGD~U7SIF5g#><0Fn`i zIY7}Bu?Q|9D-K);g$g{_aAhkH7a7HANLMdY91XOFB`F~6N%f&nM8(>t-1p_CSVcj=;1@<=1X#-ij6qpX z4m@#hy5(=>d!cxIt*+7B<+yF7wqB0@^CL~ifkR*`1KJHbDqLi&fGZROq=l3zoEJ$F zZdF1e1OgZ_=Q&m7x&)%b>~l312IM$CdTss;PcsITtm36NhH|04{~#oR|Cq>H1@QX= z=7o@G4s@oVC;}3RS#eyDWESWoO3H8=wch-7!zsWUniB<_uo9pP<}d=I0C*mbFt|4eBIyagqYoYEQqA)%qh2kH ziT>i?$yUX(R6djTDC58uD*vDsy#D!DeNQ* zX9%zOcWd4jPI?XWjgmpEBxnY1Odf}|Rqk}~ya9VV1}h4h2G&aH!PRx6C)t-<-=Cj< z`lU7Z8VLt%$Bs9@>OI1~*MA6-9Dv}UIu6kiE3hKyM1UBhaIy%2M>&p@WSIo)m4QCv zGDYExr{j`)?sa?SP3 z&;85_HyZ|Za)P-)iv&y_hS5A22*5yu(kR8UAPB<)im>v$6+BxotHe@Cb>ye(NS(ta zzo<^9vOMT|fV(KI+bdVs$1SGd{3)&>{#+zvOf)MjJtSu7X;FE5^%HJ}vM3gUktH0* z7@1-~$HvnjwS#C6Y7!A+IM8!iL9xd2H0y2%rVePR!q>FOz0kGmmXnQpmu22gZ7Fzp z_c=HWGdQj&RTe=jqp=*W2dbA434`GlS`aBXTr3Yd5uPT1_M#}5HZhXK2$}#Lpg_<9 zC%V0aoSiCt_^ExUAU7IsX|(BLm04Z4;}tEJzmMPN9a<=s;~-lwWQgsht%yAcm?R4h zz9^UlOdlA?<|vq6!S+hv;K{$&s%X`Ot-%- zgk5c7TP1mGbysd>@T-AjQ~nmmZ`Gq7ZUUTA%v#`Q1vE-x2#&)98AMnaZZ3+KP@V<- zDJv<)ZW7$A^o`-tQW~r9o!xFf=!>;AbF~hgUj67mi&R)Ix!xS)A5^L?T67;t%``~S zJPmd>6cCUEjsV;SdH6>Fmq8ws)`}Ej<-ok8YYE%xunZy*{zK&L{n|c#8&fnkDzhbc zyLa2FEM8A8NC3l0xW)AW438Fo1O)>-Z3PaU;6QT$I&>7Y&fq#FqoCseiy+wEMZwid zHCHNpE^3YJGyyx!o0WKKAnPDj+4)#HqKDraA*cgVL<5sQ6_W7g*RiTmVHf zaJCXqc(BXS0)uk0>{7s~SCLNdHt;StbH8|ZLh-KgeRfP;o=YFQm;&OWa)AMnkat)D zdpW3&fx>901NWtJ8fX!;R7#u&NbNGsfW$_jEJuMWAIXTI9+D)KBrpNAD6W=p=QvM8 z$iU5&ZeksF=f57b_4{MhGkEDKqe7RoIbUIh9ef7^CVUWaS)LMTRKf&_*{TR@f+Qbx_*!yqFZX<@Lc$ z<>zv>=RKa_r3Cwqy_Wif!jV?0{5e1s1(~q|dr9H~4j=)vpfuoEh6MWy2vOjUOn^Ls zxt7!2fezj;_8LxfEw}bTJ~Z>XO1{Sir5$dn2VC?hn5mc)WP$Me|vP{O5<5KZat5U`M(e`h0Ms)J@oh=eKSO^Z2Iulq?|EcnqWM9 z)Pb240^rIwtHjRn#m|lIcHpKl1{erg;22PiVsP8B5HJeCB8pQ6DuNh*0bdP%>)@9u z6C4~FwL^U$z=bJ&sEjKzEU%^i#EcK8R9&#F>Gg>n(s#5J5}qaFGu-zCd8d2ia`Wf{ z_q^23CB4=Vh{1(J%4FACIUo4>ivd>Wii4O+vJ}RWAWVT~9ZW5tWtVVZJ8&xnoUe!x z;QlJRSKOl){hGI*5Hxs7^$9ICK0C_=_ z7_jwoGzf)YI09E)5Xb3QW%& zxV#7)1b%sT0SX6K7b@{SeC+67dzwGY(rx^h$ptg5jZZJpQONXZfjLKasnPM<>^nMb zSzB^_`izZ29%fv)@8p+1^7|vCmi?}U4@&QmQOJoI2hLnItvGsi)cw+JmX{cm3c=;$ z#YG{H{w%V3%%N$0&xYq~&^b6-PA5@O2oZ)x6zYqWd-m_F+C!FiDW6J;1jc5Y!;0V% zONd(BSR^zTX)5Bsny??v7P`tfIEl zw2GI$d7S&)o|k>G1sQ`gKWf$7OIcd4j|E*J$oi1<(@(FI#Xw~Y$;4?);=yG~;SB(A zP|=H+fP$|8g<>KYb|~4BpgHgbPF>Wlkf?lRtYvGq>+>L2moDoYd#TAwF4{=3E2(it z5*X$ndPD+4BCmjlffX7^qyQn15FQPNCPn#(GK@aX$kA4%k-e>cn@>O9b*=XBO8@R( zSZHB7j)p>V$y;+=ANOX$i(8Fql*t+2e3$numOBE<{RwQA9}o+Lv(qe4!<)q@KCJ7iSD-t`(~yXHSC6)N428v})uEi|~=@;r;<@?BcDjH-ILV)~1KLVnA- zwB5|j5&K3qD0(j{YN_vk6#s-gN5||jlg7@R=+RHewT69a^50MC z_PW>UpYKE!p64YGsVKf3@}`3WSNL)`6zac@>l!COl>**o6kH|{2?8!d;Nom0)VNVj ziN3>WmJn!~Q7#~NOSt}&mg@cT)6OEu%RxhDwCuQeDc}C-6{PbKdz&dGk8~(}Id_kK zH?LpLFbO1aD9fxBHFK1(4GD`hocMqsCe_#i+IuLIW>@2wEZQK(Xf5%gVlp-$=&lMc z(CL2`E?<7zSDE5!E#35B6XXNDp2s-G{g&A#_Aww4_ip-B$~K{)rB$BT-aA$_(B3R< z9O)QC>#yIvNnaeB_=|rZ-y`9du^5Z?)>#uIIxBi=apQtT795W~-}+YRxWbh)Z1nFH z1XbOj5OZ)il)mZ(R^c!^te60ve`>Du(u$c}`DPY@TEP-2mEDxGB*uduurOMe>XMkH zK9XQSjs#8Fu(esp@_#FQao@hU`jPqSA`HHsJHwJNNh!BtH%q}v(OD>CtbOen4mNjf zg_HtG3S5T69{`hM7!@dOw+zD^F>c1QMzy4f*;P`+td$fotI^huk|LlDbSC&!K02~- zo9nus!Y6%-Ze;=nUE@yV3pvzl&GCw{RU>{g*^eOunzdbHuF3NHq= ztjPo#1_^e%aSRV-J0a#PGEg$ZcgT7;_KNS!-nUA1T^!XD|2AXy5la^X{+!BV9HD#Z zH26*WDN}9U=;4D+c+r`0b+r)& zRe%w)-#=JwOoz%y=jf0n*)GPPF${Oqf3X|4rG8;G`20Jpt3O@9%#Dvf_<@=q)KG(O zye|y<&EcH4IyG4}Y+A>~zQ?eCuEL|z5eo<6`8xVNoj?EB3TxhT8CE2NU8-Mvhr}3m z=mRh;5jijnO=tALc){4Fe(N@Vso#`ju^mit2BhTvQoorqZCF07WkkVCgR^(tsv`So zC2{7;9RD1P_h~QfiK`s8W8~ARN)E=@aT%b1Q|x4MX5OO*^XLA%@ZBDRRJ@(1WXyaz zrpD=*0h3zI-#TQW(5U`8wZ$&NU|^J=O3KePn(r+HhJpWuzZB9(2odqX$bhikkv#+( z^5xG>+x)V+b+-=8=1t?r7^{spC3<97r`%6a4-l#yn3dfFeUXzKhI94+yScGS8#R*D zt)2wxUGTLMksIX0SsT&vd#mQjg+ISm_77v4^^|FxhOj`a{h>s+QX9%=n37aR3KoYs zaM{=Jn^6b2QjHmWzyh6+>tR;vP$>19p^hn3MSI5#;*vQh$|3YM9Mz8p-b!ZeZDqM! zqql(tYXBJ_)D>zCe+rbeLh*3Rwo+;OJBDBQj^UxSogG4YBGt9ntaVi!s(@%C73U+1 zD|=PETouPMRncyrHn5-C%jJrfSEf;6_PYjAN^bcKR^D6iuGh2+NvUj^d5LZhJl8}U zwsZ?M`?B)xh^8G3Qd-B)hMkM@xDA5G~8IN7T1k)k>um? z)lPkog@2a&r%(i>Slb;t$~JEGL*st!+D(Jb^*(a{(UhIDe37xc-5md_YmXt>ncM^H zR^#SWt)SN3A3EK`Cz@AE>e)Cp^aeX5uVwu=&u8YFNoJTl@q6RZlx=8=huHDSr(fF* zn{z{dDL?e*vFA@+s?fr}e!sRNY5X-M8=9yiz31tt*-F91nOfkZgEh4zb%Q&nI&4ED zV_fc-NED$P?&w&1iSbm5Hq_gJ^rqHNz7!gja<4*Z_s0bED)XO>GL;LN?fY%H(qr4S z%lgbICcKAU=~g49W9e1O|M`MAk-<7C`pe1Ye*0Qa&ynN);+Lh3d%Q)4gwy}8Xxck= z#9f)iLDO;?i)=WRqg%*{#Vk6nU-?=oq-iN1B6XE>KIC>z1RG@V*O38O0_T>GUs7PO zk7?7_+|urIr|_L?8xN;$%aa*khc2A)KSFaAvP5+f)hd;x6PH6&pC_OS7yc^nkALqy zeWK3o86EartL4V{4_KoQfaKaIzZ;Xrqok+c#HckHI{dV%{p3rZ{j!M=*Cvd$Blh}4lS|%ckjvUdxqHm$&ZJtx(L)Bc9bM^Ot$*1ywB#TjQC+>#OwGP(Zrv6!ERin<|5rt+g>o_Pa`PyC};9;EJ%$PcGNhW-FLWbY>W zuI|r&fMyQ#=ZCnCkBRYM=d}xUbF{2fG0Aw}{39bk3k!fmW@IeM$n|vqWV#Srye~53=v*4z} zrZnw8QTNE<2PI;z?poIIh`HH6XMP;LKSonM)m2nBN(5u3RG2=sE-Z?2N&9H#g2OE+ zl$?Q>56H+ML3}hsbU}&{h!BCK0Fsg~g%ZGECZLeG3u1o-$j{)}+)rx`9YWEXpmDzj zHketx!R5VWqN-0idd;LKC@Bo9N(`i?*K)xLp_)qi78V6-Hcmmd8b}-kndu-<6B2bm zl2XWUB|wq`J)Bw(9-to2dZqf!GUUV-ww{5#Ds1@9bV5hfQw^%>r0ZBvu*5Uq$^ucF zkUT;VB_(4A1zD~*oW&v1lwfEaGU-sF9#Eyd2bezA;*pQ0M$b6CHE7kTS|LMIlOZzk zifT$V2b_=#9pb1+nNgDaauQ980;42kV=)F`=UE0)lq=p%dN|b%JODjEcgx}XW!G$2 zGWS{OlR5gYPfg0~klr{%aRxyUW)MVgY8jPS$dWE&kaB~fAd4RaL4zMNguRj^MN^Pe ziNGPI6mk^v5`nV%hJMgcLrQ9oPgH-~mO6h8m{OmdsdlO&4*mH-Z5 z;ExaCqDA^A=xQZb`env4NY0XX$bm zPe)l&(;;C&4q5w;cv^GiZ-sje<64$)urs}q6=dY((78ne%_lP4_$Fg|Bq~S; z?%zUer=Rvs3uXQ06^OsASL&yl@})URK@MN*)}ZK;4!-2qx%SV?S-E2>yiN7THmwK> zvN`XSHRJ!D{H#dV(>dx)sr2+eCnv~lbU^+$Z~B+aS+)DH3AuAzNG~o&i8!=z8n3Lg zC}sa|tL*wDst9V_loGi@!*-oj3a*-7oZeq9<>C{RIkl*=gVKd1MWeed35xn1ijaad z9Htm#;bR0?Nr44c0rE*ygVcB=MRJo)M0xiJ&r&73ct2&m%|lrypG37#jcZe~bP4;> zr^@-5Tc%TBz{htQOtk4Gtj=mYnWByJavXg?$tRkABF()v--Lx^jk~>_Z;~Bg+z=6X8TCsP0H2z#M96Z4L4~yu74U zE9yPm5X{)8z4fP+K^yKx#+T0TsmG=Imzk7uLxTL*LqMG(8b4YRPd|ldX zWxe#VZXfd?yN6FkC#-E=!b;u711UPZDeB$0oH=<*ODL~~bH+}%9gfiKlE-MNi5xo0dj zZ}vvhPm|6+`x~5%4>5+ZT01|ND&07W591&ywJex>*ko`5E||Ia&nFt(ZBp;2(Q_`; zyfs8^+1dG8u;aC#4pe*Gt$ug|Q_o$MVxQmLH`MdBP{(UW&joJn`!dt3@RI}1{j=t` zXyp}Bs8MBWmX#2ywf9%WFqXwyc?apW%q3XP~mwM&ja0H$FOVQ`; zMpaw)plSBO>PrVQ7>A1?y@R!P7TQBDj=$b`L(k#s3v{b}DZ2)HUE`=8pb!e?xiIau z@xS-|`k$|a_`;8}9O*n)zT^aIM31nLh*0gFhu6=?{IzZMpg)I)oEp%oS8ENX%O+zq zq_6E6B5H3fyJBuwF~@>FJ#vg}ajNCi{Tfi)jmD7<&uQ(omJP12X*4eE_u03In zzoGWpHKTD9Bn=7=x5<&3i+HQYzHh_F{6og<8$Ot_KIy0dw$lXNIp0%UxDSqbm1$i_ zuP-)b=$yM@e4ARs)TLN;*vA+RUWcw}S|S5pUbwyHY@zObYZoh8Fy1*$cu8^1*M@vl zu4TzngGav_lX>I3e=qc1Fi`F5$X7~N>Raj#jXU~n?3+tJO+P+k>ApJ=r8{Y>aTfry zYe|o^d5+ppr%i!HgT8BaV8Qc7C2#K1*8h~zI2?i{!?ky=l+8qD-SVW$`lYoBlXVKN z*WRgO9HzdaOfMko@OtZV70bPEK!xGq-^@QVTm|WIF=MnQ(Q5tdiz+^|+L#lMb3QG1 zawF5Rq_(D4O~#?lEj)aoL4~=ur*8DQlO^WiK;M=QYyg*Ja^qh@h5`!=&FbbUe`nm1 zF~$jwQ{kA6-QiS!pjkknqfK-BcHa4*aEBrFFWgcg8TO{MVXi9wygSgqI94wJDQDDx z+{T7){;}!SdKc;+T)lktsl2<@9v2iePSn4v5Jd+Fs|PH(v8LPDUbZUyPvcGna9V@G zIXsX1t`}|d3YVHZ87hAJ$D-Y{j_gsp-BrLiPKyz6ESv^Ik1cgy7?(a?yLfI)z`=u& z?X@mp;yGg2IK1g{4>8oef3~&&!w4fRy-N6wHBT4M9x$xLlrhaVX`{z0OEcnHvkJdqM7@05JH_>{QaRkPE=5b@)*hIF zqXU(JaZ+OSfwn;H5B#=`dzNR>(H8N!CjQ*L?p}2h;|m(&61UJT!;`_U0&CA1+5F)p z9(%MT%UZ43Uc_@5=8SKQY`=fyhFET7hrF3cVV~Ohrlc{%=bPcT2kjnxzYgDPR_vjO z4qBHRlx284q{Pr8ZA$FQ_-E6RGgsZj4I5Lk3~pvtSp|ngZk$@zCBVQZ$5RI$6n;Hx zR2gYjqe9Kcp0`(ei5yhng>9ReWFy^rlr0ioEhHrNRnAt0hw$^}1RhXZKFa609=4tj zZEuDNU3^=_H<;0WMK-Pf14*w(wcL4+_4|kD@+kJg)Zf1hpRB#SPg##fwob36wZUMf(*6nTY%Cgw~>syz88J^|xcD2c{ zJjTt*xURj=rA^zA^OcUP`?vmouc{gSS7dyRrN60-=OBOu$;ql3w9S$E z7Yoi^*xPTzknKf^9Jgts?$2kOlWarXuzAiuW;e8~oG;&rvPUu-n1R}=$K~|Iq2`f? z4lZ0;g`9D=`u-*Kk^0(-94VJ1?O$7UQNeZ<+dp3zdi>j04_Efky1ZW!4ms3hDKX+| zz4eR6b^0Oi*~ep3M*9W#A$N$D3hj3u{MHUfDs43mJPLjFrr+*Hx2}#Jx#0P_rQ6hH z+)`4Z{Rvy>mf`o}1K6!1cRz3J$(4Ka8**<_o4qXTxeP~QqaRq%(}({4bKd3MRkwbj z?a|GGNiIX)YuP4Fj^+HnTr}8P^!c41wyIs;P-4GcUf^~uT$ewlw&|};wWf|JV_2S& zqvR6OjH6v5s5a>-`)-t~RIuuJ@>1qe11=urHA;OcuW`KVMcrKAw8Ab--BNwk%(ApFuO4>8PQT3Bz)jMRbE!x>))o*UdvZ8_j_o~PL(sxPNtb|$V(Gi*6%sx z&cXQhv#*Vc=tpXoW^_f*<(65QDaOM?-<-JeaP}|%5`q{HEh)_Xeb;I5Rlx_b@ia0FNB#l1_r;L(%8{Tx5TzH1?RjR_SL99 zGeTBuX>@h<1by$$;A+!2kEcCD?BNE_M~dtQQJJ|%#d^G{5F#ne=-H_~!VYOJMB@~w z52HX=wBkC&xntG6*6M@8)jOt#?l;xIV06WOz?ln@09_T%b+t zX73pi5eQN5;SpiMEdnMvfCb{`0ij{K;}0@t3v3bG99XJwLFq;r$Zm2rvn80AVU$9Z znmVe7`5LzAa12rdn8QOt0)?IsAnIOk=h8a=qM)3!gG2{mqP}}GXgD|}X`!!S<@?7` z`fcMJBvHrnlkC_W$OZR^0K0BGNMis9l-HQ3VYj9C3>ocVhHCGJKzF8^diFb@0CF>~ zVdh^<24f>bCfG7%us)#>tekgt(ts&g+9)_QA{-9#+V>CbF?qFJqKT}0-#}&?g!D>E zW(U=l>=V-q&SFTIADl_SCfkHn@9p5fU(JQ@g;AT7&mb;1a-{OIW9KtZ(=D<>FB-f5 z7TvCXtJaNLH1CFE!?gSt_H2`rOoUMy^EIsMrUrPl7Q$?y;oVAO-B`st5%T3jZ5K%C z%F!%Tn;VDIVPnq068-wj0)_yoneQ6xt%Veji)7(Tjl`SwxIm zQ$CP`)~ZZM(-2Nck&Z4Y0=dfkj%tOktdkgzz{JP^0+UxFA6MOY^>m7`llWhRXrjU3hx~N z>cRAoT1jCM!FE$}IXRrYnbq7N!<}DOzYlngXttEX+5lSAg=|iyr4|`)7r5ZBLp0cwI2Flm9BYrXhf6__a|X1u;gCWkr(vRk(qBVeYN>ZfSb*a% zKsOU*f^iYg6jL;1Ra`wMqH8mf9N7F3De1Czj+ltPE>6+w8X0|0*9mFyd%E_Z&>6-n zwI}W2VO*$7dt!)%y%lXBf=TK#5RGAX^~I1=?y`|Yz_~K<6uG{-WnRTE)gcjEKsa{T zF5r`1OcoS0^g(vac#=10etw@;M!1w@mz|0IZ`tiIJdbA>wfT>AX6u8%^cq% zN0VO`tRB&L;oQrKoIEz9oRb0JHaOh1qsH{<_V0rviX=*>kR}RHNaXGwV}~jYf!cvl zV)nZJ@4O^BoO&$$pyQ&t(5srZ_asqKj{y{h1J?kHCPSo9_0g zQTIA^kp)RG_$j`f6Tzm&7`XU*fKR(@e2^|RGql^2@o1j?J&Sb8*(bpMzl>5KL?z&2 z{+Ce-il1sYuHsQ5{~t9CmlLD@mr?3}8KqRO45uTMgLQ0TZ*u6~>Lq8og+0zhP9hq4 zPi7s?Rysw6#}NO^C>0DcMVQ5rX4&!~7l_NZ%aV*Um2*?KuI=*6x%hLBM(?fG_SF;H z|1wHxtL}=z(Sp(wq>D;HMydPWr&mc(X4|ajA6>P!;WN}M;KIP4#t-U8TAlNynsJMjfU<4?u_y|VzY8kTvycaiw_*~M zn#6f1MK1~{4t3%@Tl;CP!I=Xxc;zaNoH}@FaIZtvPWui!v1Ou3pZxN}A@!3~Te8Ig zu#Cx8Sz;Mpq^z*s6wTr&C8G?6GbB{8Wnmg5P_@3V8B73E8|PMuz-PKzFPRN`W6N zNuo4H@-io*qKrG2IzcZ`b4%$(Tk`rZt5$m`{e0qbv7i35?QcHUl#05fK_Nnb4N~tq zegk3vvgpwR1Cj~OMwC?)34)bLlt3v-BypAyd7Pq13X+t`td++Yj8D23Wx1gj4dzEv z^PJD|glv|3_rNY~dOy0G)@Tw6-kw2rCz=qDi;Tdb8Z#u@B4lvc!gi3ELM3zt5LzkIBo@>F+<4fe!yurJSTn0wEuuDB`SSB^d=|QWPOlLIEFK?#Wr{y5;JMo zf^$C0>y_SMdg?vqrxb9%@Q@&|BRJrfSxD?h%LL2NB7rgzOhS-Qnu0pc5&^s>gV8t; zcs$7&Xg%q7py;zvcea;^n%if%1j3w||0(I7bG4IH;Fkp`lit)nX-C8g5Gz zLus7i6;6O+L=5s1N-QwKkQ|c+egNZS&&GagV~wnr{~`X*dK(PIU%s@(h39%T!lX~C zkvd$_4dwt7BjJKXDN)JZsnB$kL*ZJIQ6S||Mpv{7z`sCkS{HW19Q80BUtv7;>%M;0 zF8_?WJG8^NVy4}?)bFW-F|oYi;;gxda7byO<<*iL=mCe5rEq})4_*{%3qq|;S!AKm zwcww|A2*#2jQ3!a~4R z9vDRTM?XgyVpgG%is%H_5)=g%Wk{ULLt;(Y3WP?6BEb?T$l#O=bcZD=AoNM~q0mQ{ z+*H7DzFqZKf86)~w_sna>x0Lp*B{}8s=Rbk+ki>Hwg7_*{FlISjKD(1T2T-%_=Qmz z!9vzrj6qpX4n1*i`Yo9CIA2tzsZWacBFr}yJZ(DOq_3=!R=A}XGToh-NQ&If6qalmmi^S#c0QWEQ9yP|4)awezAPX_n@23b2RfKxhJyiRNeyN>Nh)J`aak#0fzpJ!jw$;Pt?d_@_ey zvFRq?CC?|W*mG#!PpL_K{663vFk1;=+hr&(PGEpFP&}K2tj$0`F<^~>?O_>IqA(u! zyqt8`s$Amr_L680QuTKr3}P?pz)ql;KjJa0i{ofop-v-auzSaOPQB zl1VGeVG#R3k}M9ypCJg)-nh=EqqUya4;+@WQ^g~5mPc-%nk6fGtev+vXR;(wfIQ1i zatH;`MVg`kQ7NdF3TE?NwyFhX*FuXdFsN?d08>EiC>g|0f@a_r<#AYF<)$Yo0+JA8u&SV!V6BuM zU|lyJaJSrFzTNUgt-q@1YtB*mr{*_ISG^}5jw-ww<71YBcuBN4xFGe5A2`|obB>vD z(n3qDz>1(l0pg6p86yN9m7MFDpZl2=WM0htHPo*Sv`D}JVi=Uy76}%&qBKfD zhHQ+12NYrDc`IpUFssB;Np<9>>qwi$s+V1-p__{wD#^#0V&-NlXwqlyOTkG5!3{c! z?9D|&71fYn#ly^^a{uZl+|0sVPcX8C;}|1TkTxCVA@4WNi`TmUF?IoAVKU?yQW z+yW`mDL7^<4>}Z{CV&>BD8)07=^KOKD9{}W1TAnPo@5_>Y9AWV`CcHWe>v3dUY1Dz z43MJXm`Pv3F9pjQEQJFj6%6%^dugj;4+5AixETa{3n^e2$nmIEL{UX4A;LM7S(1@> znP5bT=4G6QjO3vAQN%E{B`hek)L?7(_vNv}eAW$JbZ;k7s$Zsg-fp=f5G#(=Vd4Ie zRb2fInbLul5oVk+6BdTWd7xMlLvS1h0nWHphTDwdC6s4D_sU8fuuLR@do+fNOlhn^ zIPb$fNkOI=knAXwg>nY~9wUNENrKRN&`yKi#nlq-A=kMmzNgZ^!+K=B zAYZOke#iA0y-j*2qm;rI8U}%nvNN#2$&{$4~KdYP^&>X<1XZ;JTb?h@2A2?DH1B5?9JAOT!@9GJ#vnZ`LxgwiIm0NMqC^k}QH6b=G@$hODY;$UR&?U`S9 z%CoBbD$}zM#?QoJp3__$7}5hm7kfA)Cjgg3QD8qAFph!92dobc8Z%IFqqsz1C@7+U z{Q&cr0}VW^la8+RLf_u1*EjxC%s4R1*jL{kDK+Abw>BpyGsFc4hiHNJF5brdNac`} z>koJjD-T*c4hKyzi@~h~-$D5ViZcaAL>|;3!ruen zrZFoyBf_CfHmr&Iz|Z->&)*&fdk$75lpTFkHV*?9SH0|_GLz(nUz>6&+&^-_BtDkD) zQWSoEN+hA(IUPokSgmj@0i8wA{7Rr-1#5@|*$3dpW2`(5N&--IxaN~|cOKK4)I0c_ z!pHI7E=;VD(Wh?y0p1#OlAe}I`kb5NMp)NOWPxLVzF=?zvOoYS5mlT%P^h~DXFz5N zkYY&CxpJLnc55!^}vXDng_REv_`tM1W@$GRUL*ZnP8 zfF5}KsR_G1`(hn$H6uw+!(-i*!Bz3nx%N0I3Q(*JtT++_egqs1;v*QL2tpzm8ptIO zP?Q0gS4R_+_8xHe+I}md)aB+mzx=&Ha|4}yzv+Q@_@atxkaD-D1gUTo<5E(Z5wLC8 z!E`T$yA&i~_#;7W24a!I@6%wI2V)ctQjvs-KvoHGlMsPnVLcTju73PH^d!$$eYtvG z`3Cj-w=D8>`j820On-fZIjCFV)bBunID)b&#D*5ZqJVKYBs+oPaWpINz%79L2}oHI zPkMr{;=Tvum+}|c-u!&!r}O8{?$~KareMvd{*0k;zr zV5g=laX(Ud0mb@)TR_gI0alt~L0Y8*;9MlAJqG4(LgHBhJR@XD0^!T8E%EFH*cg}p ziTTS*%>%D?>@v9UQ=)v%u>Gm=Kk?^7Af=d--BQ2#IlmDg<`E7B8h8`{cPoM{O^ZNV zNEQQap9qp60~~_@>K%#@z*z}c7^OpJ7XYoR3+;;it?fnXyJ3q1nymY(UCS0zzcuL# znWTV!@gOP|)gv?rRDpmM3@FVcupPltf(rx=$`TH^7aA-ZEHL3vD~|+)0BC-|;l$kz zPyyO$ppk{jZTJg?@PsVf$Z{~^}fihV*)^LHWYS}NYK7Fb2)x1S25O;E*c+QLD; zI?(mfsAwgu908XKc&f>8vxCM*aqIzYFi(&&o3J?mN;&J|E!{XAt=X^Q!dSl_O8s#7 z$!XJN?~a&`z&W;BK>*TU8I$w}i@KjZP=>F-7!nHi3T74&4i|vS!hHf434uyLhQXJI z1eGbUDoRN|98J(1XtL5=TiYBb4^Lm;cW~pK)faBehLtd+M!3;{@{b7ALkKEf!UF3E z2it%w(?FQPcf{Qo;8PlF@MXKZ_zV9I`N#KZUcO0( za*J}NrU;@2pZc$Z_g8_lqBstY9}K9-NggaK9D#wx8N@_N1iLLSfVTqJ{29jLYUu%4 zT<>Dj;>TBgzBV>f**+y&NvWkzV8*sNtPxK7Kg7HKIi!vM>E6^jQ ziX?^Opdz5btq7`kV&MJclvP|EE4?T;u~Q@YWu|G5##T8%ocMX~wT3e@&+u06*6VXY z6amcu)XLISvw)k5@(Kf`GY$rDz)o<_Qg{Xc9Tfc{xKn~B0)=8C7>Ov^lAt+2>C#~B z%(!MtO!t51Az9k@je6XxzqdL-1xh|L91<{!Vx&d+N`I057)R`KotT@bKepf%@ckN`ze)WfOv;0f)IQ!Cr)-5F{X zAKmHK(Jd=Qn|65ztwNnbcz+2J7uoC<_b0G~0Z49?6QaghVkc+@Qc) zVKm5pJn)H_yO}EXs}8cqoB3}i*2fW*=h#m#s$MP1^ujy1l$)m)$3Y59NdEzH8<>^M zJwifyfM^E^Ho`(|B1ono!BFiME@%`s8$+#*1{je*1OG7cNq3ercVE~;QTtO0; z7%`cK9JK%u1OMT`Oas>h`Zye%x`A7vJemS&Qb?$ZvOXh6l=fRzXCcO2|FEU1Jz zfLJ6v+Nw8{44me20PL3qEH=CZ%sPW& zAR7r3n7afpk$@!v6dM@aiy*ARRW7=@H_hF^#kw_U{9=8Jg&n7^U)A~9>(}`ZZ+Xo3 zux@YxJGnOSZ=>LXf!GvqPXaeSg$tf6!(<~v-G^1Qf4z5kRy4?(0EPh~K$2@_9 zsp10*qc6^9HMRJ->2cDg+`K|7kk!BJiM(WJ8SG)dW&Q}Fq;)Bq*C5GCSR!2Vj+~Tp2ki##QzKLG6)V8c=gI4|W zPyTG9v5N^TpKkLDM!uf-_HpRL(-&_Idh}UDVIdVG?bEUaT+0yi zV}@Z1R_|`|xx`>0{DIhIKlZ8KJJ0a8<$N|=Tb1r3u#lVC{JdxaG2 z_f5Zh7j~SRe0gn=ZL{Bg_c=vgA#K06@r{bPZ#t8~6ZD|*Ag0NGqh3?}g&=wYo-gm*cjT+Il%U{l{4$v(MF77?9)m=(YJXJk1zT zvdZTeWra-4)w}gqzJ4ceKQPh_|1Gg96%0)Rx|rDc&Kc>LwlP+SRP%hxs8-+QbPrtP0UL)av?bz|=pFw05vcBe@t9Lz#8~MYz(<|36uG8={jH^N}`9*a)mE}R# z1KdSv-CntRI*Y18nijbix^~@ivT^US%-g9g1uySDr@{Oa##AAV###ja*X?*k z%jNIm(@jJb(myZ)1waMrF1H|M#&}2x^V&Gyyx!o0WKKAnPD*O2{BdL&q zce$DS#k&)Vca86}W9sr;=_-y22^qM#(oL+x?)=w-wtj!CdWLiuMTIP@(P`?G%AFSb zth;;7KTnf^X(#fgQ4AHbY3R*n;_o(m{(#I2_vY^QeY%XGLTq_nOp4p``rxMWbGh2{ z9#8O6f_;bUhCBQz4dbVfFZLQvbS<~`K|VC|x=Oys2BjTts_bT3L{A|D8n>@9l|PqgOuH$SGvvqZh_r6a*qHwd5mU&FJl#W&|Is%?bmDo80SKKEct>dK-WLREH|A`qNPN}+JS<~wiJEZSuDI`2g z#%H+i3Gz<&$mQnI1@3vNolAPHok2}NUJ?cmevDWtr2T}T!BeVFXpyVMi>NWTa&_=h z&yn;bJP_t1L`oq{)kTj>^qu{vK%-^x6aW7Gi_b1j3dtJYUV&)$5NacsQ`$B*A=|1w?2NFmoNcIaB82fOiTwr)kDhOcRz%3I$Z$mF{9 z-H+5;KaC@#kZOyV3l}GytkUf9&x>rq2P>t*JLJ>GMfe@jW&)~AV$LLO#ZxbNhbKl1w{q?Y}zg%3*a zkx|Hr83)c>HLW;$cGUgSZI+i9lnTM+nNtE`9Sj_qjbU z`(g_+24{ZMs=1f4^rwi3LUx5jM=8O(E#je&-d4ZO zryuXSR(p7*fA=pev@jhZ-MFz4EQ z|Gr1(=Gj>2mhdL}dMfM)y3i-x$25q7Lh27(J*d#XLq;|1U9VBLYyP8Bq0;@hF;Ga^ zLW8R<&$Bo#-=%fSsH%r6roRX%@dwxB+V%e{GfpnE`~fu$Lukb z>X36|?41$$D;2%!D$jd=PXSD3Ncq!lh>6Jv)p)*W^oFiOVWGl%Y5g2Z-XA6@As0$QnIHB8lZ%ZPp z$)O?pnbM<0O?{^gVvG)n2oH^b7-L(o0kKRdS1KepPp5z9-0a-mXZ!BGzg&Di8NZ&# zIL7^!*(UZeAQAU&`c%p`p`oQ!p4i?yRy5GwENvX=7(?rC`okOCgpA~%>2)i9y>ZN| z*4kSSjXnnC<%4kzzN<%!-Lhb2og0(!!5KEDykNNqaagcK;gHEqsW%T5x>8?S9aK1w z$X#6$2)va915!D*Q~nusRxUbSIOaRP#p|%u>LTnLnmfaiFex-7EZlCEf|dH+Vk^hj zp5c&LN?W0(AzImC!{HBr$uW!yl(t)jVQQWS$?h!QYPJR2Ljf;d zsRh%_kTcm)_+I(wD6Va;>v{^G^cA<22^bKQtwzoH^@dQZdKQ0r@bxO`pgQqGWns6< zpXV}t*D}X|(oYwheDi2RSHmE|Za0qMIlBaPnj!-wGkiB7`Nv-Io!R?VsjiEodg9+^ z>^@@YV!)qMd5j};FP-Lpm7@<1y|~$Df$ezK9ND7Pm+ohVVfexOa#hOusDAJ1Wqe2V zZm>1bi z{1W@2-%0#_h84+Rms*jeLt+g3kif%4mkbs50 z2d^V18^v!PRe0Fzz^?mMWM5POcBVwhf3UZcuH<@sYS_3R*o6f%7!8KlJ$ng*q5qfg zlU4bd*4IBc*d|0sa$h+poa+%3(I--%f&zFh66x5t^N^n+OCc9Z(2(=7#D0gx0cBv9xHv9NFw z9eEJyL52XhxY{`XqFbM^M9VT!-@O@%UZ~UaHLQI9I7+{+K@xR5Kgo{Gfn0Ep2p}Ns zq%bA5XkKHYhTWFlGi0=f8LGV>0>?}hK$?2?JD>n^Gp=FgUrYvLBSWTM+F*S`4F*Hr z*+~PY;8devxZCZ%hPCe>+GFx+yF?RN`M!b7wxG~JDG0JEsF$-(OfM-|3<>j7t}2^s z6IQ*qga3Xt7rqxpZBjmixZuc<%FB+O&pb`H$jYH^?EYJHyZWtKH)_$m8;(V*DHXXe z$b#)_Sg&j1LU-j9I1r(klH5^!w>2!Gju_a^6rA&N*jJLmpl#yVes1Qj8WSEYx~ zkhX2U_OniOJ%>Ci!OEEh7Ta!!bv~9kTVRXe`e9)qVa8Fehaq7BCig4wt%Y+@-%ZQE zcE9?L6`iRc$I-LOjz&NR!$8Rpm(<@ihS{FYwGy^q$iuCkzQEct(9W-6CP2uIuGR{+ zYKYMc2yERzNY!na>g?M6M+ZV1Vw`^plL94R^$fB0kccorQV<0WIIpI1_Zf&Wu~3G! zhZLUZdBzbc+p5$ZXsZ{;yp>)Y%rB@>dvRL%S(PoYxR_vz#spjROtALFQRTaK4OqRdUx3WXv)qK=Kp+sYR>68`^s$IcgpV; zS7n-UbE+n(b@zu(&G?Dt6&f07(?wb`| zd-#=r0x7J@&82vV9iM#qwcW5eXPLL8&(N8Re=VQ2V&JT!0hc-BuPNEkL>=j!%sV?yfWC|s+0){Y@u))z00r&4r6y&XvJRq@G} z0%#SGi^XUrB>qV?{Y6RXx40WrK(y{c`qyKzCoOplb zOqZfpbLI|TmTB~yD!~`1-vkY+%Sq%h~KZL`B422Y&)OSw1G z^8Hb_p(TYhE#*U;`{Xn&5o{vVDX!}x1F!_n$v24QuXfsgtl)x|6I-|L+Tf7!aO$=^ znE`g_vatV0Xs$vAB~-$JwQ|zIR?>G7pyw=IH|ggIsKR$aZ8D7;T=GeU-9N@zsWNqp z@gJ~8NspK!NUo(5JW6^BPK@He$(}#^(0rMru;8CxZeB9RcqYwPOB*U3+$l-pmOT^% zLeSbOB7hIFU4AyMpKphqSL+sCew%dcu!{EGsON+emtZ;f}~2S|Bt<^0B`DQ!;vxy3^>JMIK^pl zC3a!hph(f7#km$psV!-NLUE@^v0}xB6}K^L3>Z+fxVyt(SaJWqdm~LMv?(dH|HGaE z>1~eOqu=??_r9;PZm>Z-)LLKmYQSNGZ_Q%0O6UiN-+wW!u5^1^EXb7dvxi-OomDiU zCJp_A7~0?4`lgM5<-Uiyp{-y1*S*>l0l43a&+Ux*-y)cyT2P#`{LZoD=PGs zWo^zTIm+EF_9c0z(tjZ>4?T=MS^bl@EBT;m~qAGJ?yW4K`^6%aM zDE)4iZ5{6}%R0;G_n&hrS*0jQ76{t%G{*hgCWz$M&Gauj_edw828 z(o+kXC{>RX{kF<7Eqjw3eY`j{dUmVZhJYErUS9t%3Y0mybEdc?ijYBwvlsLcT)^KX zMx0Z^vOVco>C{5k(XUY=Ho@jrk(OM7_p~Z&Y5Lj z)e2f#%2{e37KB#(=&(L?7{m$&1qyf5W>I$WlPGep1UV(1AOsl|t(L(U4G5}&))&F5 zI2?2(b!r_*#8Qj~lma0K2zm(V36d2QK#xRSjTJLKm92R1zt^i525)nJek#h@{m{x| z@D-Vyg)TzBJKbd2ZOiEDV5`+;+wJ`96lM@K1>uH^! zF9%xMpoqx95lXG+K*R)uXmrVD;$_an(%0|WXsV6&`h7u3Ha18%;c68KC1RlL2twK#R;8sW7Nmwj zjfw^vodp}7(x#A&p!fyZn4A0Vo=-ltf4@F{PX24MY4Q$8UOUyWqchVeKyI5zI_X zQsz*aP*)smFW8IV><{CX(yK61&~;Q1aNc1EHArePYEDnVhNZ$XwYdURa%Y&{UyLh!QcSxQCmtFpzwa?7R=`U zIM>DE)2ftSz1sb7q3b_6D?=!Z&lJ2r;+fY>V5J3pG>QX7KQ*htc+jAv7|_q98BRm% z;IOOVVLt$2MZ$V&6py=NQgbaek-vrnhoj%xjEI}uxu1J!7y77NM6V`CLA`VAc-ZK1 z_kQPns^Kq#Z_6?z!I~UN&u?54qpO&EdUx#BbFagb(z5}i(Bm0Gu_i}lXT%O|95Zsj zo&hCRPtSjPw+qLpIN4o6vL;7+SIw$(=nkfzd9CPnvPt2X^vv{(qgj)qTMtfjE-`3o zg?Cj)OrAJw04K|;L~F@7*V9XJVB=MT*j%$LbprR4jpD$CRBLinvh$hhO`5BGMrW&a zKBift_p)zIwkAiKa?O49lWP?%`kblQb^X&;vMfrsCPxe9Q2~=8^KUq}s@?AsH|`uM z`+p!@1Ml7Zm!UBmN=%ZSZGUQ7wuL)oS&4E@j!w%fH*JvL=K^arc#J*W~E!rMo^TN6CgAyUeRw zy>rwlXHTffzlOMD+B3%i@{$8b39bcAS2dyHK(!kV_JZ8BN=<9D6sUfZppmQs>0!`& z#Xvh198Y%4b3%G>mk8<2#@>`Qtrq_CwDg%W~9fa#Zu$qnd3_|KuHi zy+n>9{l=Ycz$dV+*=&J?M;SrDCP%U{GoBVY*Q9++_bo?uR{8aCdQRM5mx4`>Mh)n@eR}S@ zD-KlfmaV*9NhZrIBy4i@bB|gVw-+B%>v_q;2cnw&aW=gpA%W$}HWCsZ*{nhr5??_C zYpj>5bI`JSL)%vzw(3;fYtEi=5eWUMhlgYyjd--Oj!&2Vt9 z!GYfh0x#4U&cU6S8Z_1&liNG8H;npFq5ATl_k_+iRR3|rzUj_-TQZvbN{;$et9EgI z^}6%+|J!47!+@g2Wto`%N{*(4yo}0S^6x@t0^iqpQ9FA(S=ObRk|UIx_`BWkd%^qH zoK|}-8-82nB*V^U97(Pro021W<3+D#6z+RDV5$GKc`IfgcUE6KB@!SZ3f;hp{y1PK z(z9AMM`$okN9t&j;o!DNrzbEi+{*~QPAy2GlPW!xpn`5rE;{8zt|F*t<q66axxs8k%GC2$7(*EmCvIt)0y zaBvcHJf{J_HgI?)WF;gQ?pCZz0XpGbT3Q`T6YK3X3{E zKzY5#%Q6W^s2mO3a(KeV6O|tHcw+o5vg+3H&aQ>Ra!qGE-eX zUOv{Vo~+TAFb5@p;-e>J`Rk4H+zUdc>4*1M9^3K27cpO%83s%u${$@^SgOc7VmKDP zLQ=Qt`mk474Q+vf;@+(M7_n@nVO7tDtp*gHk!7(KsDA9pPTO1GdNb5#Qo{wMXVuTr zNDFi~&N!}!+YaL=Sx;}~@XWIr2~n_*Ty-7J0)LRIMZqpZk-!xRHX5PF2^<0xFbGdjQJ9W`$Xp#P>9#m@2dIb`7-WNreAmJ@!i{tv zAc#pC?rZ`^6>xugg#-lv?S-Z7^%S@THF}cLY9L@(%i|C$4580(QBCnAiE~<9L%=n* z?Go6b?aetQ#14Pg!uA4;ge%Y$-?gv>lATb0-sD9n^*~?;|AH$!#o*iwLRNg&!ggr> zWZVHsml^xL>B~_S-?gw|*r%!8!5tbZ#!j2@mjLdfG&G^6wXBAQ05k|c)6g1-LB-Vo z2w@l+;{HgMQ(2>X#D(3OhuxC?T?-o`5rH+bZ-`}GC%`(PXpGZC$hMk+Fe$xCN71;J zU{orKfCx}6Nva9n25hyYPC(9m*TQB5N&}H#OORL)pda70u)k|zXDo)|yB2n!!`;&t z;U~UpVSfR3A}eTN+mj5X0jVPxDCTJXuZf#Ln}^D%QtwN2s`fr+PXC%Y$#m!vf3DWx z+O~%By970XI`12FvC)}M3*B!|`h!2&Xi7SOwdVR-K@ur|9fV!gwU`N1bil6z9$xzA z{EVv`i|<(Q={G0wpQ#KGYcqc_Vgfztwxnp-hp11_f}ftbd~bMsIt26*V-FmV4lc(_ zpu4&LQ<9Upy9aM>nnlfSchE`kDxqZ)*VvV4iSIJ>I^~xkCBDnhTTV_|0RjUM9Ubri z5qdq~9~m_Xz(}6ZYE|HP1muxgr6(ENk(8$cO?Ky|?=tk(mT?(E;=2qzXw8`2;ueJ_ zi(eeZstRtGDNYN>J@BCbI-8(0dcYlG1W)1^aOD8v9I$(4)Qy*eU)++FofqZ141FNo ziw~m%{2?kCkh8QNpqk*FQmJ)nHAVxHUk^?c7Vz3C8c@huJ;CVV9ztodCIk1c2;TfI zLvQwkCm#oH_L?nr#r>0rG7G@V2o$-~hEc zh)GoA;4@4mJG(IP?=tjB00s_``Qi`OsC-^c&_H1U^g^7Pq6r-jQ$mjc;gEn9W=X){ zs~|Fsv9-RC*uJg5%h2P%_MRN5!=cT;%g{3>fNb+2UT`FYFfEmU=lCu|FK{~q7+7Nv zs5!x%I1Ho!U@x}1qj9x{hDoW{>o_gJ0$&FN#xW`y$iaXFOiS{dTBpM`7?n&GLNJ`5 z3NN(scNu!CQ6(ZGysSTw5R6AE8i3xSzY@qC^eoQlXiCGWSO`17^*~So=y)yI1}sZ+ zYLW%M1c1sjK_-(m3oxy0S%^)YWn}2ni;4x2HOrNW4Hav9lo9xc@50}>M#n9$b-S1pTp@y9TBlO=0?T|!vJ5vk1r{ki*4xW%0svsD6E*=CdxZlKI6ijc}^m$Qkudp!#O0;F3%(=2SggN&|>}Yim%?zuRC&e=Sohl zeZqamSHL(V)l_ePqlnRMM5PCJ%eEX;>6ca~Y!ZnH>?AEg1VV&)wgmb{$!x3Cq2L=d zg&67Jdcp!NP&-PdSbt~6CWy}_fF}}!8C#2bh$aV=WZKm)Oh(qM#V+a^rbU#(PRv9I zRWb(ogV2SYo=H91!#1ED1ZGWdT8NY=$+H`#C<99LF=GbcYf@(C`5$ZOLgMNjxm>}p1vZbH^A7l)ZxD3!E z#DhvkiX%}n)~2p08D*B&l#I96*OWvgG?4h&G90DzU=&&;{@|14sn{JNcu0cIX^&0* zGpEmkfBQ&k#&it59kKBa$iz=qg7)^!N5$gHZ*M=YSj`giA8hcPA{i+*y>xCuo{l|i z$#QngC%14-z`M>dHG4%Yxjt?6y$6uxpOqlXGtFLap55@xa%^pMl@Eh{oCA;Y-0^$% zFSm^f=rQHYz368};Zc)k9pbxgE!BBUy?l}97dGDpkB*ldc>H;7yjJbmahn?C__!Y) zMIM|Gbv>$fo~2smn*2B36CVB2runW`A20XZ-1}$0#svfS!J}QrgM4L0-|oB<8_~5? zuPOh!Ew!#7phC)vYTo9BqeW;4=rbquLY2%~2M$V5P!)O^ zs@dZ7jx)aG6ps<^QC_t1%c z6MfH~TDVypUbp^`3)=7l3xhO#)KK`-Px$jWY4~g%Yo8djbAIbTn(lu#V$+>dyWLRY zN0ODou9-v|Ug$H>FjfdP{)~{YT(YB~C@Wu|=D|s3+r+#Bd(wd{Z|ues7|&uP3pBTC z;4%gpQTQJa*D4%X^i?>6mP-mu+*0|!s-Nx+Y$x;TcU^KpRGGI_$XX5Hp9x}v6lQ&S zlfp;*(JV3VZIxB_G(R;Dqk5nq92gobRKE=flh+k>=#z*#f7#2|A1TT|%f>s3O#f}y zpuy%{KZ&-yFbM4BrtO+IvD7v!!3na|3O-P<131}aho?P*U>$XQ6dYs>ag#{$kDu!B zO(D9LH%c8dHHCs=dLSpA$(fb!>-D6K5bn(zy#pPjtVeOiuvVdgvPtGz)HOkFqSUu^ z6yPx6`yP@5_PqzjcEXzr`tT-&wL{I{x9W}y-PAzL!LA_^%YgrGNhA1UL2|39A&_qz z$ohwJg6udH9%#xfm0PqLp;$o#KEx2BFz+P_`>jNgu#-T13@D*0QdsV%TZ#njFNbm@ zb1c6wi+48%`OFZuC(IY&MNBelG7!jiOg3Iv51Z1VfoxBqv3A&v6hVbOBq+$A?FmxZ z39V)kW^ZHtjWBSm86+9bQTEv@z5{z2+SCeC7y^R*&1&evl#oaEh8Y!P^b&@lL2qE!rJBLF zZ_Sug>u7!{`EWWgY@G0AOV7K{wrgZz*83#qpcG1Gt3`C1@U+| z81**5_HR;n;vSN9-9)9))~wMG9Ma7b??$OraOupkfT7?p;3uPijhIwx868JzNEMAG z*+3R1o40ub5e3;jF%QWe`(?_yCYisJrcFl7D9IpMo_f)6MiO>156R9n*Ff06Y@6I? z*2nc}E?q%$oDmMAl8tFk!i5F|w+Qs^YY34#QIKW3lK!3cf-GyTXi78<5)4CAt_@Bn zH|5$bO?x8CLft&5X^>ga>^Z7tmL>F700r676^Q?RJyxvt!p` zEPCeemwkO2({pG?m{(S_!D9ZlGN-p|xWvX)w515EfoVyh z=ou2=?;$ywxLEVIg#&`EyM)2k(0V9r4Zrf|Z6{50Zl5VW<}<}BID-j#Bdj?DD9P8F zvnp*newJp!N({6U zRI~wL7X=H)E8dhx5+w}j23Gn8%Wq)aOfoN17!hJ)CBLExtzyeL$+|W-*@6`rxRS2= zY>A4*>+A9~ecil_rjm8ywK$cm9adlniFuq1&hL7*fs4wYqQUd zHII-4AC@u$ocLlPzz>(mX(~PMtP>j!*{R+bh=ti-9e?#E0fT|}x4Y6v#@#DNiQT=@ zlg>6q8j6t~{sJdslA)2rD~$ZN=#=~NclX|vZ|B}$C8t(wNxMNOI#23ZZ>Xf{xRx^? zdqlPZ%P~vLQ;8{bAP=L|P@sE)tMpnurYC7$qk#jTAO%g~S_q|QC@pX_0y}{^Nd{i# z41|ap;0#=}J??RZ4O><$dgXa4_rT4vOj=VEce7i=5-WT?R1NYTSe&O?2h{0^sm;u? zM|^*}n!m?@ySJ`Jn(zB;P2TnUvXSIs=lilDPI~*v_(iK{05~4gw4s=1u6ach9U-3L z-=gD&H!8$h$x+PpijRtgG8DUa>UhKC^-rY-(zIr#C3AbNz;E=**0+3fOzkc0>x>?`yk4mF{pLF5$mC7yrya%-9 zRMJ1w(xFK*y8hMQ1-j323oi8J=PWPltB&5%SQ?!_G7LkSM*noEyFz+8O&#iNLH2wB zUuRU?)iAN$vx6b|oWK5>_L<(d8TZz=&Lb_p<#g_DK_{0}*x~_|>ZEZ^ zOM4OLzkOeGAwiDk8|%wYSy5z!T(+$Ny|VkFnM|j;Qp@G3(sp?YQ69lS3#}CQE4Xxp ze~rTGVqIv=U7kV#|HzeX;eyzt1abLqH1EZlfe<5{z-^Uo1r9gxd3e@K{&}KR;nJNV zkNo6;c`T9ccZWE>^mXx0(6!OeH*y7n{5z0rmYe0inGYUx>* zx0bo6Ot7aU-=n5rfQs?f_PE~j(*2Wb4zGHDu=_FgQim?--BVI8>zgjKLQshT@a_qd zthM=xwH+!o@tSsYYpd0hCq2PNmDzm4`DHehA`X1@=>d*NUc3xa)(tjz8;wD>gHO$S z1z#<$7k#(?csI{T?_Gd#quwESBTM>*Ca?O<6)bn!0 zEg^rnR?^!N{U4%-nV>!pywPYgbp4)Wq2O1x2*t5Hes`Fo4 z>s}ANxZ7DVwuE|H?->&l&{=A3;hzKSHp8fR3?Hwz+@dJBwVDB1gj%xiX-(9hD`$(F~?|l;bj=Lo?|ueh|?t+Cgw~WhDpvbThCQq z*Qa{+g4)RA+`!7|5hMTH@P?oYYo%Snt0+fx_Hqj~`$v!SqP~KQ)m4s8DmHw7!WGgSE4u_G!5fraeX<4BBQE?njYiWw5ArKRI zFIbR8Q?U$iE`&mOiI;VIto52;QOHN#?z zP7UgR6i4tBNkgM^8VYA|TJM-RfrU_xDh+yhz5W(S>38o8(II)?kCh$!;wHB%%oXY( za&&x+^z@(m2k1IK8vkIF@8puQy>^Z|R}BlBS^ymy5(FMY@j8jO3~!M1q13>g1M%D# z#p1xBr{^f(c;WRL;LHG)M^?jtt{i23bw~XHha3}l^yJ99lQ!?xxPuSZ9iwLtz=KaZ zyElDp1ra$aBDv76=KG6}{oWSshj)4MO!nc6hfC6>MX<@e92(8K%J5*e;H(<#FO~)# zLyBj09LwVH53a^Z3J6VbEk@bY3dC+mnwxzW%zsuWJo~KYrF)Z#+e=?Gog%Zp2Xocf zvOi>exr&G!<)~SQ4!>6VKldiN>kc3De3rBF@o8G%lkEVG(y@A)QV|5nYiN?jK&lX= zmT(tm3O>hqU_YVk+dfXdH$a!YN9=$c_z3swv)`r1&`GpPCLK)Hd2Jm+ zu2B+^qp^>C8*)vzd8~LHwI=rP;-NBozn6;!j-}2z63lkJ4%d@7kahu!kBU%(U|deihXY8HM$s!xX~dL15ZYikgAq zCqhBN;3l=eK~3OVy;`efFh&C>d~gGRTZO{`&#VKt3#+FX4V91(h&UmkxrWaxdzBqN zs>SwoB|OKLeq82_v-_cy&#iBOzkmz}KMvgsD2Zu>^?^w5rh?)Kr3T1CVVZ$%JgH(d zq$tj-X0*6Ug#%Ph3xkH#0B0VNY#v@>9_C6HdV|jVak$;X9ASQLIpSU&li9~xrC}|$ z7q=Ii(_Yv9i?l`LsNH{8CykbG8nx`u*ITJB4?7~7~bL-99Ik1q) zS;L=M67`>fy7zxyVz2Ks|8Y; zM_uXIs&7a4$azVBwN9mfZq4l@vkx*#!<#{yY0NrOx*NCvq3v=8i84D@x zkML_=b*2ba<@RNuTPD^DuR<8vTYL$v$2~c4Gfd(C`p((8jV)XTD0;3obPAw3}Z(+Po z$iTj39}&IxM$Q_=dH7rOt zu)tyugt!3f0eFu(rEG+@g>1}?d%I-GId^{1maSB{_S$2^idB*AcFseY{F^|qB8Udt zDq#Nc>j@yp(eyrnzy5Tb*m)_cayEIr!b6qE5?^Y z^~E170}4!^FA_nJqfgqp6<%%n#rQ1NW1pc;U^{31ddauMn_VFAUoaOSmJ6NK~* z_3pjvUTC?i&GY0t(XhEh^Wy>86X(W@cvK6ffhq}zkpx-#x1`FlrAhG4+=qGu-F5mjal^S{vY+3xpoRQNu z$nIpoJBVFj_3yA8MXGgz+KPrFgzbm`>KhExYbX}@#z_n;Fo>C8RF2gbvT&^VmBPh$ zHNQ~p#gfGfI&>VFJy3SZxtgcwPlMs)dN~9+n%SxIt~TY*dwRJQi)!Ai_lNY_0v5j$ z;Fe%?ntde+k3?HQP+%aGaLxzi09>P{;Iyh{;UbDxf%i$pkvv0@5E-H8c{qF}cuO2} zfg`^y{zQ@QN^}1k9XgNr=>=ISkMTfy;!i{c1BF!t!9mXeToX1u0~bjm$kEQWm({I&(Fy05vggYsNDi*>3CrPsQiR7Reo@z?qwz%2R8``78YDn z&>U!uKy@kDPhcYs-h~-g%%M|?YDzyd(*>ktOKNsSEL>6n4gJ3icPG4cjBTzg>)SwC@-GtoJ}?eSktO z1*r{DX~Rx*LkMgVL^r-vutSb+-Tv^)yp;vs-{=sRzwe32y$&?oX_)UR)zHff2e#Hz zCAgS*UZVoL9F`t9H&Hr}UxTm+FuyoZ7u10K9jD^#-qDXr$}C{rC{=aK zkkH=iHfs^`=}#(V<-cCvIBVnE9_hJ{045^1m^OvL-emU?hbs?EiK_{ohC2y};vw`J zL7z#_X|!6n{pIz9n$oZ&$HC1BZF(zaRJ)y*485N-V6Z$rF5#Y> zv8TZl5(K2d0m??4v61Zu;f4WxZs6RmcJ99qg81C!e#SyQbo&-J3p0xK#mV&!$RR&$E0j({-Le-0|89|7f)c| zL%MC9mjUGkMmTVic|n`Z>`=k5S!Cq+sIoN!GiTzP1eaAU*pV61B~9L`3pT0_9K zBE(D53^+D$22R;}5Mt7D3D%o^wuo6Rj^I?UR zHy3isS{$}>G=34RR+!L9Sb`3GLGB5-D>3W!f5e7eyAn;#tMD=IP>pe?T~={{s02MJ z%U^Gl=UxypO+UQ9^4N~9skoT+#f7Dcyd#EV(JLf%tF8}wmDTWAC@AjD%8wDtRvK3I zY}jf*;Tc&LjfLvRp6s-}^{qEUeI_+rP-RQsQV=qs)>c?XVw6S(VXzQ$aO$eTeQWk9Fs5xq`IejOt~qZYC63>4 z=vrZ5;9zXJ41tC2=K4=bPUh|&yt!!>HM`wG7Y~q(!C#@xLuFK{_a!=2dml5Wf6bia z73bUebHm!z=quED-=K?)&U9MnetXg%{K-aBvJUnN6&>*FfQOgxoAa92@VRuBke-u6>8gG#}p47q59$D{*B`&G$Au`rx5~@tl{7+Ks$681+EG$ z_*Mx$4q-U3U~5UeiozhYn$&?rYC`CZh$yhe8i=8Swm554c&m4fc0X4AyUxA2Q{EPT zbJ?B&F^wyDTEiTywRZvP3VChH6S*#bU~c#2(GPkp_o<&XF;}Qyof^R{E>qo>9De%m zy(UHV>DcEJwZ7Fo;ZnpEx-{q6%r~Bs&th)DYhO;h-Uj(Oan&Y>$|-7m%co3)Wv zsFla1(l6_DiJ-4W?s7Y;Gv_zdL`zCC@FaV1 zlyzBmA<7CxJf1gM-joy}7mkxs_^_O5nLPa!%xEU&O*I=v=U;9GK@b@v1 z5kDL{)v9EUYMB*bg{n2(U-smsZsqj2SC3+e5BAKw4v zc+Qm#4;!-w z4abf*&jQFQw7K^6^?RO2j~jIU%-YS%e`%BjaaHJwS9r(MIi7SmNMGhR?bFvh;@mFU-21FyK~@NrEl$Y#!1b6a)Od=r@wUs==Xcpf zR8d~-|1#4cs?flYf>jnrcDr-Qc%zA79q;U|yLb_jfp{u(ZkOqh2i{ge*ExJ~=b4j} zSLn~go;YzgvN+(r6iJ1KJ)q|u;2umX-Q{K9e`c-Dd)T?fMW~-C!*En6XxLv>@8Z6D z3*QR(yZ^D8ZkZ58g;v$-IBRCLj?3kn9^CXR&}5kFj=V0yP@!$3?l$927>Ff9+++6T z@76z4BB+p|z?y0Ixva95zG&=8;Y)#qONbn`3JfZyn=CvQHLXmh+k8Pi=7p?k}%v|jh8}b zbV237sBBMX)>yi#>8&W=%#D^pAvv+3Z94kLMJw4Nf1aB?DvO|`P=y(78=SlQD%+&`m5(34-Tqys#z>)CReZbD z>OpNinyXv!@G%=&r*qd&05aKj{e(vufsjH!FVkMSJpEMlX3u_KW(Yh~H676*Ul$*R z;*K4?6Wjc0j&4&X&M2CF0FV%IwlHT`Sn@;^Re&x;Y+gxy2rO_?&Mke zYapV~o}lnT<#iQmx9j^PZ|BaNeVp{ zc`4N|BhXMNum11cw&7ZuoA>>m3u;#NBD z5$vE(b{}1Ufbj@R4pL^xHED!HVbj{X%?8@aZ9!?jKj%JGh%<vtu;WSE~xI@a7=Ky@oY6F00|QU2$}6UK~?5hdnD*E5$eyfT#V?tQ_=1@%;L!@TiRcCWGYV9Nv5Cl8?2Xjb7lkCWRp-q8WUX4y!K#8zxx- zB%?Cd8X!F*ZR6d#jaRz5Bc%}z_ihr@`~KF&BOVSNHo4i(f;r#Dwwo>vpefm)OF9~! zmg^o->%&#%QA0_$tFzst5=qo3QX-kr9{#it{>;`eeOP}ZM*wX=P+vBPBmBd98T*9w zU=3*D%$?FhlAxFq)f4Dm1RiJaz@Bs<%Nx7# z1O_O1k_A>@HISZZ)oS=3XkY*m4&16ZgQAYrxxKBG#Cv)twD`>RnnNWQq;?guR>Qmy zDuVq(d%)VF@DbO=mf}y|eF5Wbg4Nd3{IrF*W7P8p+RxBnq55s$h3WzUNKjys>VMhG z*B{9N@kI~Wc=4YWqA;b&L-My>{exk&0Cfk zhO((=A^?iCAb++eAfppr)6&6htiKU@+L}R<@fT&Ey&`}Qp@pjs56Rlp%MtRm z6_O<5CCO&2@TUWNgu<@U4iuuH-wH~TBr&0&+qY&+s&zEKlzccH7&cD$vZd$UXWKQhFim|DeizZs zyH)GPEt+>D@X=IoKYQv+j!(}1#`KpALK`tTvY7-2q`l#OA< z^Y=ISH0F33Sc-rmN3x(hH8HVfiTc6-ym8pjxCj8eUSlLEt&676vFwIETZui7J zB=q) z@w=^I8?c)LiLJ8P3Aw`Fujh?oO2HE8DIFbTH3c2!UQ**)JV#lP$_k&hz+T=GD2wz# z#sG_5m}KTpx8x1(0MWwn7L{qi@|l|G7j5Vsf<0t?Y8f<1zi#=3`Rk)C2R;wUKKoVU zGj^QS+_Bi%t;|jbyM{|_#YLxZrW6jW9+IPpi#2~+IEmQ0OBieofgw#ik+gOCZ}iF?7;_Cf08jzx!MEa#p@Ak@O08whO(FK$80(MgQ>(nW_geCz@2VcSpkyhz(g+tT#t;K<$|IG}F@U`W9y+sG zreJA*78XR1o9bx@G08`S7(@9eVc0jYs;XL2!`hl;?xrwm#J5j=)oWv)bCR`hZn6a{ zEK6J>OUEUv?W)g~m}CuJo~Eyxm)caaPP|^HlC{Gs?lwiBcrb++>0p~fMlu!ppA!Vb zc<~vWw!!MKr+4^bS~I2S83Ws1I6;Mg5Qo|Ks|52tFa%EQ@UIb!bF1%H_!wdg^7n_Q z3GOlYWO*oz1qCY%RXIARJvRBzoIVf!?IWof(=qh6^t{>8oB@T|2hDu-CIO=YROb>} z!_ggXX^h!1pWMPV0q;7;)a(_p%i>yYHiFA~UN;6};TFp=u z7p= zEt%V+ioek-Ti^0cw(cgy%lBP+jQmlXe~3vZ{VOe7a*~%cXLr#qTI(ylS8%Yu!F)q4 z7zdzU4qKto1lyq~WG=dCt*?5U8d*@e`PbL;3e8iw&4}tRJ({)-O>+_deD&9_n@~6F zTmDgL)bEonoW4@Ig`f9;wwy}(XIeTmNk;Zq{jbxN(0ze|5eOt@@bPnIp7m8nZ)q%z z&L0_uAx)!yI@DbuJ)Ncwb+#aTaLQM2io|x8Ck$;%GbLKD+^;vzUNLBaN7ss;liIY) z`AT}u+15yFS@uD=-@HMrgF!bvBHs+ffc>rKD=9dPA;dg#RDqUN#mN9_9D)I^))R?-MEk-$McQ#<)^GDGD0rf)_`8w zebG#&Q(b9X`no)Y0{*cRv-?KFT&x)=(ZUJbHtk!1Qx49#p7oM{o@iCLbf?H8zdo^j z!_UV~F#gtRwe;l|v{CXUrl}@3Gs!qg{uQhvYJ;2a&~@!+lzXEM8Tj%_lhx9*E^jS! zQJG**Nq%gZf+02oV&Ke9lRUek_lZMAyxq$k*@GMi60zs#mm#DT9qJ>W8k7w?>ub%PDwMq`le;8XKn z!B>mxMc?f|-pw=8d)H#=_Ow`#DdlI+UiWoY(S({b^bcZae{buXHUf7hxDKtITCi8? zsEZ{Fpn8MRpwmI420_@Zb1(Y2>@R$QFAT1~gF=lE1f~>^v=H2CJ>w_@DOx$&G@+jw z*W7R~;(0iL_PRG%Iw|pY&bzL7lgVFc=)rn%d~ZcCPRP;j_;T}?F+JTo<*KHLX?nh( zG&0dwvYHNq9F;47V`__Ap0&br=WSEua?AX(EUUyIM?)&_D0QS(n^t+>m0xkZ?P4!k zmeyjBBW?-#!?lv$Cc0Fv$pxkP{bd=+kuBWDSm;Q#AV5lDpf(5s>ZDrGo7C%gHLvGc z5J}R4R4k>(SdP_M$%Gs@vW0v^DJ_e7(x#7%k2aXsb&qevy$)kr)Ho({l7Uk6I}1|f zpmq>!fXGEhMFp4XG00Kg(&+S|(<6Ex{`ri@=#$%{oK>++rKTCA$RI}pjSn{L*{*_pg2xtAqOvW z4(3atI(xZ=n*F24dD6Y-4Ex=?xGWu1#^xHQG_Zx}Xt4UN`UWJ()ZuCsh{9qlD0I;( z4Xe`96iX9Y4UPkaH_t+#Bc)9|KP=e@WZpK(-3M!mxK~=tsZSbyIMlyT-dJa))Kj08 z8K}-6M^U%pxAZO-vaEB#LPKtrY+Eox_nwT=XON?L6(&ZEuYauV<)4OTJAKS+pTiY? z=2d8bt;jI7;fJ8e>7$pPm#?nxxkdKE?v&)>ii3);AYE?R`}H72&S~@{rPXjGrsZ)A zM2s+vnp9Cd=xB0UTtnz-J(2w0FXm*w`kXZKIUMs6+H zr78__RB6!5>-D!tO22z&hz`m7er$SF9#gKVg=GsIzJl!V3hCVlCu8~r3SDAI%)H6jXU^o-7$Lh z06h4lvwKsD^xsyU1s7{I$Wal=g?2UHUwrKMwrD@R%adoatgY4{NAsT*3eP_4dFkGy z;`Y)PO{d80@4;NPey8X#^Om0nFSoJs{dEy)o*dRx7^1T7N>^))!X$kDLTc{)}( zvT$|Su30&9V#nG!yK|$0Jm)lOjh1du!42q+{YZzo$LM+>^OTUJb4=+e|5Cr>k0T9(h*15t`K%~0(IIcj;J zQoGg5T9>Ngp~zirX!F~$ETG;XNB5ik=~a!H*`~N8K4Qf?#YkDa^QK`>10w)zAyC;u zs~2Qc+ManOJkx=8Gp=X=1P%U59t=Pn2N7+K1c7f%gQ+3B52UI=w3^g0j83J~;yRvE zCzp|zT}I}k-MV*OW@CR9KU|iHmPIbgUesAqe}M-xBNZIv=y_$YvcpHU*uJiW=h)JZ z%e--RKeRF!187}f(BN!LhSmq-fmsDDOei%hr-o@pF*;JkXh;GC>M)R}$5kqv)R0;V z>_rVrb40Rvc!_zKo3+pzbmou4?H=X`^K;7)_v)C;KHe$~Yq33Rm?(B<3ZRW4AQTP9 zawP~@<8Y40bR33JI7x7NhS5_hEzjslEyrsZJxIW_I*>dCxjip2B?v`BN^{-X{daZJ zX!)j5%O36~%MHlB*x4=@_G%#%-x%VjwEQcsMX)yzYC)cyA{bb)cr8iOI3VZ=ou0-? zHN#^J1?uh;Ppj0piXCvILlW(y5=mI4N zIU3p|f31rVwd-$f(cJH7kjEC;x7TujW7G4hb3QIA95wQ^v|ui zePs4QMroL{$=I+~n^`os&8+!a0wjt#f@0LH2IFy-rx+~(YV@3j)`6X^;nfVGfj{<1 z?T?-tx9%_BdPu)sM_$Kt`1hAvBg zTGouXbJRCRUhKF2t5;75UG3~?_cJ}7A-WE7^oZ~+aH?RA2WujZwHx5SDUS=ss5seO zq3j?>)i+g*K2@*V(myUdUJ<*tT3C8!I_%EEWs}nKuX%?d6cp@#yjn++pzh64G)`zK z8a6!|uEsQa4MF1^O;CCklptA^Lt5AYA+33TIc$j4wKx>kXP5hjjs@0rUnk3|>JDydI}?>l?vWVMbG*zIk$O~ND5LL#8`!1mBF6a#GxO1%`$;ClEO zCuwk;>3LGaG2lMa@HowrDdYmZ0J)fZA-Bi)uvOg)o_xLk$nMGY54M(lYlR0n%D>=2 z?2@?OM(?QhchM?p+((v0H6G;XcFB@+?);)HTd8pEwa11Pt0Mb3sr!{pmQd z^HNmhZ1Q@Ahd6tgBOF)sG~h>#g7ptDs$O6sE5xPG@`G2xuy?i2gB&r($wQa#YVX!6 zgr6zZasDY;=2m);qfgqp6<%%n#rQ1NW1pc;U^{31ddau53(Nb;}YFl;hF?Lm%u@46RS?rQTq`A#%!F46pWK$hk79^~ltx_s*wS8C`z zux0TVb4E_vAj=|(4{~&+aPeKuFI0Q6WbuLy9Y<*UIpGKAx?>*NDa^Fd0-@h zgTB?saLfgc{I*MaMZPP|{cm*WJmRMpWTiaD1L=uB`MSCfaDq78dPpj-19Rsdqip)Dk}P-fONDcuSkwyI3&o+EVV!=2vV?K2{p~(JOkCGC`<)T zG!o+}OiPo53S6e(e^;qBY626gtnz~kd2@2GT7~2mv-MQmoX!14`0TD3vo#l9Mv|Vx zO~&az$Wg8b%YX3mEj*=f^GZ#8D=y0;%OnaAaunUC1S&LH_2d6VI&S_wDLU5Vi1X-#&8ZUiPU;!qU83T-QC$XY>({zb}`D)zF+vW-UFTW0j8w0;3^#m zIl6WG!!Pqz7I=T7LtOs8CnERCGO-eb91WgT&7|4uR;ToYj(<*QSv5kIDYPKKliJL0 z7r8l_thUf}W?9Xj!(~}l4ML8lM(-RIUpP?F4=WP#uUEejGABv!KjVb+WBo1hKD1h; zhD#Az15~jrT$_<_Sw>R?q1BN>?2kqd0f)f&YjwIpLVKUF(zaRJ)y*485N-V6Z$rF5!&TgpecoxUuE$#OG)g+P`qw ze4SL$vdpInAxCBAb(+}x!Y34W`^Ca*B~Db6eIs26IXYaIe5KNsSR3Ew8NvLyJHXjV zk&*!iAp;icO@*@z55~F{!wC#y)fxh>6)7HWBGecdZV+{=XIZV5OR(O|;RZI-t)b7G z%`IX&%-X!J)2k033L$50%v4M_h(U&(QMB497FV+b#lj5cC{_b=7E@6yMr*ZNg3wST ztI=p-kHjrjn-INhZ?H`z4leFcz<-3g^st|>Hy3isT4XFs&;geKpM)zTwEmCSuxnSM z$$1q%#vQ6L?zGD)E}**5ld}BvMtSZ9A=C83`zw#_=$eX)SzlaOs>nNHI2OG^Qn%{* zuvb|PkA;Hb-mLr>v23MbRnLa41{9u=Wzkrue(cFk+gsmyGt_5N!v&>h)z8vcEOa-{ zIIf7>4&x_TPjBY%%(Gb!iG{)?3s%g?{VAf$f%3k426WM8QXCd~)NM)8un$q6o&`TW zbNSxzczeKj8jt3mgQ@T$2dot~rpNUiJIU{!h@ z3(-5mt)LZEWPx(ofmKkIFw^W@y>7Zi4t5*8bp75YPU;K_yVB>ODwcYiAtp*rO#=5e zkFmT~PXIJUMd`Gd8n!5v3WE@H214{PPESBAj#zIi{>55xb1s5o0Wk-st{U98W}gCM z+E$crxw$SK%BP9rHyqkZXlWskL&s|%Qj;S|O0U95!BL|kAiS3$)LnY&0e7V6RZo0lyA-cGp=qdzGK0s-<%X> zPkke?XP2s2aZtv@Nxgf@e`Vm8M)JB4DJdQ-s)YW-H%oOu5)khl()s-T()OG zOydfkYBW2rEE)wcuo&H~#A!W6z$rq{G5}a27(EN9Lk$i~4NHT=6+#CnLd#f>*$#MR zTX;C~+LR}9UH-t_?#rVe^jhvyKWk#HP{BGif?Hgsx-B{U^xu0;it5v`&nIeqYrDA= zafL3;c{cNn=j5}PTkzV~69-18Ls2s2db$E{g$l*ZC^6uE`0k%Jtc&ctV|cOu5406J z;x(c`jl9_^EZG0+A~sK>NGErq6vOp12y2BZ&Mt6v?3l+L>KwWf*8Ni4zgZh;g<5%B zD*dufmk9c5^p0|WUyaE8I4iW^e65&XxsOlSSlI1Fwq9kcXGN41iptxkbtw<8lm9-E zY8t&Qvo0M5O``QC8K_u>V5|^d`$Eg`_c4(XKO8#Ms$`C8nH6D$sx{qT_T;5*<@C5$ zk7BAfnoNtYLU|T$(bP2kFcFrhWRFN1VfkWi+Y^H7))yc;lwkDxW?Tw4Y|R zWS!jmtN}e%2$e0u$NIqam@3qGin8&x%hl(1*+o=QUhV%f(;%wQz>tDf7DslwbIEw4 ziD4b@?5(?a5t4y;Ds*m_>5vEBRzcS}d}e+8!4~Pzv$um#*?n{YnhJ&bjnwZN(Ducr z$P!b--Iamg9!rHVU083gMWu(CrkT#~t&-=Nlap8I&%~ZMaW`@ik_rubK+ikCJ(yOy z%get1%vzl{Q{kvk(6GO%-o<_Q7QPkmcmHEG-7+DH3azTuan{Ug9hb{DJ-F#tpvf@T z9eG`Zp+eh6-EGF5Fc3?IxX0|v->rY9L{K3^fj85mw|_Xat(3SsV>s(Kt7?z*ulPH=f#fJucck*VAkp;_tW4KKJOgtcsjMQSoo26?=yB;n>uZjn&&a zJ9$rJwSW@PA~qW+;Za85rclX`Z{{wWapK|OmC`?-{@OrR8 z^;gD9q4v`PM$D`^twr7zZ^9?u%j@f;pCkE~$Y`V#($-k^tW3WJ@kJW1dKvZKi662Y zP737=X>!ZA>F6I9tz?V*d2aToEP|3k6=t+;aPID_Y?JC&K7Ram`*)cdBZY2N@$FKp z2etKRu5QJ{$82bw&RstN$Yk5~6CPy*LJIx7Ond3_^i$QFJ^Ou`A@ESubVP@IU3?UZ zJ9hL=Z1bl%x=oolqiFVxFEa}|3eB1ApJzhnS{=In_)o{}8_RCaoY*MzG+WI6Q~3rn z2SWLl18#;4&+N!3baL*&vo~aGj-DI;*t5;*GQ-nxaLG7a6pFuIeEr13v-_P3Db%o2 zV1z!CprR0I#6pYp!z;deJHPJ8)txJ)b4B{DBG4yQ<7 zt)&1>%mK}ahSLHG8z7Anl8MyDLeymbgWQ}^1sD8P^8Sbn0#Jw(Ua( z-^#Y(;KqyX9}LTEI4C4pcyozgzoUx^Y%O+={TOj89rp-!&?mc(EUrUNA=Z47J0n?n6VO!W;$&_4|_ zhDlC-0f}Pxha2H{~Is< zdp(1=SwK25;ads=*H?l`_`ZyJ4Mf!;Hf9Ea_SRVkU(GXq?|kZG_TlOmS6Frte{XvVa32;hh(MRZx@G31 z0&nQj4+WlmS*)LwtWWA?V$d7hLE)p9T9KN}Akgx7#;feRS{vrpfAh|e+$}xxGbj*t z{19eYU6fjoUsO`8mzoE<#ylkm%FQSN9$W+oA)kE6wZjZ&DR2|i8gM*8VvC^zd{9Gb zG3=^6z0ADg0?@sa1&uQ+x`i%p&5;a=>VNSZ9QzZs7%xF?KqKBx@CBtv*144A`vUJGODzfk1vA99 zIzQb#ZzqQbS#Dgs<}J(OaENUm1b}H{+O)azA3cg)FIy4HbL-*0y9_PK#l?*MNr}a& zy2T}xIlzkui;Ecq#3w!woL~MzId)Y|hEW`ALf4vAz=*mk!q|nR=vvaYwC>aGclR(a z&%DUR%iRrD^qvD~O;YO#er>K#uH|!81$*m!IT*WvN71HMK;8G)vX|kDZD2@Id`->Y zCG#4dfkR`a1VeLHF=H1biuAI8&gkE~akZ+=(!S1CCL`JTbDSYIv}git;CX-ArawYQ zAi6t$HTT_b*BLs21xz+DhKd>YgD;gMK1h+G5fXmaXKZ&jIBxOy#u}Elu9C{Z5cf?M zWZXrf$&ko`7^-B?5ytm<*VN`9E`geNEB`?Z{R@he(106a_h!xxdHIc9+wb>Y-Tc^H`vdZthygoUJWj&4z0^Y6?ftg7t**l3*qA|1&vLcP~4r7RGaELC6@qrZ6 zI5RA8HjzOfV`2A<&i@7T4g{sCUHi}1^AwyykIFD!BgPx# diff --git a/.gradle/8.7/expanded/expanded.lock b/.gradle/8.7/expanded/expanded.lock deleted file mode 100644 index 412639b6b6de305dc160357c70e6370ffe6d09ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 TcmZQ>;+?oD)qiFI0~7!NEGh$& diff --git a/.gradle/8.7/fileChanges/last-build.bin b/.gradle/8.7/fileChanges/last-build.bin deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/.gradle/8.7/fileHashes/fileHashes.bin b/.gradle/8.7/fileHashes/fileHashes.bin deleted file mode 100644 index 39cdd189d49ced87fc3850637181b83ed9bf0714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25547 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 diff --git a/.gradle/8.7/fileHashes/fileHashes.lock b/.gradle/8.7/fileHashes/fileHashes.lock deleted file mode 100644 index 03788648901993cc03bd92736ee0c8076e45786d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZQR+Zj`OIi0ha0SuTI001-q1e5>( diff --git a/.gradle/8.7/fileHashes/resourceHashesCache.bin b/.gradle/8.7/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 21f050aeffcfb864d5d88eda0cc9cd8564f7e6c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20741 zcmeI3do+}31HdPd+(M;De7lyNrXpI~CuLl^n2RwZl}nUM_}uA>lF)sWXcL+(T_iJ= zvLtff#oCODu4>00;m8gfC``jr~oQ}3ZMe004jhApaQ4> zD)3J!uw!~6ieNIDt7Rk`ss{)JW98`$8+OeRU4^WqTJSdIB>ev1wAlZX%JH;zz-fJu zC+#ZuiDSoog>g&BlTXaQS6I_vijDIQ@|2IfvAmqLI>4zakY9}5Z0dA~TaJx$9P)H0 z^7Dl+W7Yv~cXWn7=Nvm_spbeceH`*k(`HdvnFSB?Yar)33X%@({WTNgnUM3uhTk*4 zy&VKN^Bm;2R?`mmd*7r1Zto8H{r~WmO)*>nCQuz9e=sb!ktHHwtdWq zg1qYJxurRGs~Uh$FMz!IX`8RRf$b{5neveHgY5I0PCp0(oWX#+&39i3%ehSmxSa*$ z&ypDlesUaaUaV`7znXj7NmtO~&GwIJ{LPOwx2Eg3OSqA|Z3azEYU{=H;FO^kQXa9dGC+nZkOI${RTaDJKo{#v=Yz^7$F9&r8g zb&D%S`hZisArFo>vV6R^yBToiDCE)Uw`Av>9qj;|aUSxR62t_o;7$6{YC{)0aO4LKm||%Q~(t~1yBK002M$5Pyti`6+i`00aO4L zKm||%Q~(t~1yBK002M$5Pyti`6+i`00aO4LKm||%Q~(wDe--#{ddHFndetRAYOA6h zbYH~vP+DKM8@1Fb{5lPF|7`U3djo&iwEMgwTBBY$qmM|;SC}=-JyJ|*v+-Vg;@I}ZI~M$?c&C1gQfbEMJ*bbnL<#@2c$+Hc;qt9zi`lrFX|9&*MwY#lc5j*l|*{ z()@Ui={voedwAnLtySfeW|r*QVwulervD4PAyi>Cee80*-2Y&-K7qtZ!W+j`ISa{K zy_Rm6lt1`1Kg17j{3wV}_iwdfCMq8>YgHMr#T!$v6GjER0fq2{sgf)W`Zsu^i@)=$ zc9!lhMh&-vR?Hj9#T&})|Bg?|o0GaoucBS1-f?=ph;)yj(#qiCFVRAQ%!)QAwT+AL z#-?p1exe`Z+gGuH|n_EPxGT(OZQI|=<6|W zTHy`)V6;oFqL7uB_)}#ewQ&+}7~50~4@4@2>`!TAhZ;3=@WwNj==L+o)t0r_6Rb~n z1WKOHmiqKH^yad!-6>q5xH9Palfc)Kbr4d<+~NzKYqR&MjvY)rm*)45fj4{_CNAlV zc~gXf)vsHqjybk)Zyp-lqY#orDrEyy2g*e<&o4^ME*cc}e>wiez1c)N$CR zcy+o~HfZrC(i|P#Z3TEEJb1f&@=3iBQq7Tcv+oUT@P?Oz@=|5?lKF>CLT*fyWuL-FGPL7rT5l9J(pa+YKx&M?HZEuzd37$& z4a+e&6SOZVc9y*;+j8vEcFII6nXft+K`(H?@5Ne^?VSJOYUxOMK<@nyOC;;2q>dvY z&V*!hbG~fdr^}sJ!k%W~ja=EB$BFZdAG(#WhQ1Yz58{o!)9aj4JTo6Ft#0kg7jk{@ z#+`rw@s~GYTBpW^eky!X2HsdR&!>U5*FmHA8#42YkwCJtKrvKNl&)h6jxQSbQn zI65Wb4V_{&Unl*cFQGSwtTJqZX1TB1TTiA3j=gPj&t2(verUo7KZeMIMDmasozr$b z&MG=K`y}2l=__FKWqveI<$PxDbpP!R-pK419qm-2UE%MkZ`nBi>nOaDp&jHr{QSP* z)ZKCu<*$}Z;EkypWno=qydAcuyiNT4RcE=PuKE02rCM)Ry|Q9(PH@f0Y5W);w>D?C z*wh-7)ixXUdYWIx8-K?gW`WPSP#YC(D+@)JP*272S?1Dh%^+G4xDnbnT1fVrBP6R{ zq(0k@RyUo_1&j9IlTXSt4 ky+yx&fRVQIStkBSTYlbiz|qWU`R)^0uUt2_SxSum0sl*&qW}N^ diff --git a/.gradle/8.7/gc.properties b/.gradle/8.7/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 1a932724159e1492e018ef130527df73f7dc12ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZR6+VJ-?n_)6P0~jz$0RT581RVeX diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index d0ae76bc..00000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Mon Aug 26 14:05:28 CEST 2024 -gradle.version=8.7 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 3bd8695b3d4997eab496d2ea707d427f442755c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19289 zcmeI%Ye-XZ7{~E>H}hVOMVX|iR7P63Oeu1T9=FuYGzg@WBz4%d5ObL*sS$X~$X=wP znWc%C7CIdwLD4MKCX~_e0tzKwu%L{h*2eiiO87vLje>(0Te(16hHwKKmim$0Te(16hHwKKmim$0Te(16!>2X#2Ma*4gY3D zKBmSUhirks*VeG(QydW4mHN@Mnm^11)Bgu$d*s$ds?%?Im)<5X&kyV!bbZXTK&l|G zQ1#W;iF1ee12PZt`uOqh%7>+CY<@C%L-W2qp)N9o_lUdX56n}BC1*dBGjAqu91L{tlpUFuE#=u*#OrK)!;^8)hbsf-g|LyCUphsjB61$$j0_S4LgzvHn@ie4O0P!^~oT!>xDBkC1zZD?2ssb?mxa zANlSM#f!9{QTo2!BoBzyCOipg3}N@vMSiS4uBJ$oreyP@$-_#|r`tG$v+sk_gapWP3qB~MIhkuRLQpULLzB~Oa+y3rnJ5yP(APoDZCA+P&rofljGg*>CFFj~@C z{gij^9(m@DCim?8k#4qrV96`1l4A9KhTpSslbS90v|6j0s@=>xH%Xo~v88J|%A$?= zH}c$T-M_1@B-3?c$>~)fF!$rn!7~b=01BW03ZMWApa2S>01BW03ZMWApa2S>01BW0 z3ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>z(y!=&~Ou*3=b3I!}w(Xj~HNs f%lh5(+9t2xSt}>6O;&!UtC_Q!v(~m|L!R{)Tx?9G diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe deleted file mode 100644 index 41544bb2abf45ea5e50324f9dd215989b0fa19bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 PcmZQzV4T=@S35Ej& diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/build/tmp/spotless-register-dependencies b/build/tmp/spotless-register-dependencies deleted file mode 100644 index 56a6051c..00000000 --- a/build/tmp/spotless-register-dependencies +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file From 1c44852b3da78b030e594dc3db776c4500ec1686 Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Mon, 26 Aug 2024 16:54:25 +0200 Subject: [PATCH 03/13] Chore: Apply code formatter --- .../model/ConnectionDtoMapperTest.java | 114 +++++++++--------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java index 29b8f328..7827752a 100644 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java @@ -1,76 +1,76 @@ +/* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.connection.model; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import rocks.inspectit.gepard.agentmanager.agent.model.Agent; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; -import java.time.Instant; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; - class ConnectionDtoMapperTest { - private ConnectionDtoMapper connectionDtoMapper; + private ConnectionDtoMapper connectionDtoMapper; - @BeforeEach - public void setUp() { - connectionDtoMapper = new ConnectionDtoMapper(); - } + @BeforeEach + public void setUp() { + connectionDtoMapper = new ConnectionDtoMapper(); + } - @Test - void testToConnection() { + @Test + void testToConnection() { - // - CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .serviceName("agentName") - .gepardVersion("gepardVersion") - .otelVersion("otelVersion") - .pid(123L) - .startTime(1719394483600L) - .javaVersion("javaVersion") - .build(); + // + CreateConnectionRequest createConnectionRequest = + CreateConnectionRequest.builder() + .serviceName("agentName") + .gepardVersion("gepardVersion") + .otelVersion("otelVersion") + .pid(123L) + .startTime(1719394483600L) + .javaVersion("javaVersion") + .build(); - Connection connection = connectionDtoMapper.toConnection(createConnectionRequest); + Connection connection = connectionDtoMapper.toConnection(createConnectionRequest); - assertEquals("agentName", connection.getAgent().getServiceName()); - assertEquals("gepardVersion", connection.getAgent().getGepardVersion()); - assertEquals("otelVersion", connection.getAgent().getOtelVersion()); - assertEquals(123L, connection.getAgent().getPid()); - assertEquals(1719394483600L, connection.getAgent().getStartTime().toEpochMilli()); - assertEquals("javaVersion", connection.getAgent().getJavaVersion()); - } + assertEquals("agentName", connection.getAgent().getServiceName()); + assertEquals("gepardVersion", connection.getAgent().getGepardVersion()); + assertEquals("otelVersion", connection.getAgent().getOtelVersion()); + assertEquals(123L, connection.getAgent().getPid()); + assertEquals(1719394483600L, connection.getAgent().getStartTime().toEpochMilli()); + assertEquals("javaVersion", connection.getAgent().getJavaVersion()); + } - @Test - void testToConnectionSuccessfulResponse() { - UUID randomUUID = UUID.randomUUID(); + @Test + void testToConnectionSuccessfulResponse() { + UUID randomUUID = UUID.randomUUID(); - Connection connection = - Connection.builder() - .id(randomUUID) - .agent( - Agent.builder() - .serviceName("agentName") - .gepardVersion("gepardVersion") - .otelVersion("otelVersion") - .pid(123L) - .startTime(Instant.ofEpochMilli(1719394483600L)) - .javaVersion("javaVersion") - .build()) - .build(); + Connection connection = + Connection.builder() + .id(randomUUID) + .agent( + Agent.builder() + .serviceName("agentName") + .gepardVersion("gepardVersion") + .otelVersion("otelVersion") + .pid(123L) + .startTime(Instant.ofEpochMilli(1719394483600L)) + .javaVersion("javaVersion") + .build()) + .build(); - CreateConnectionResponse createConnectionSuccessfulResponse = - connectionDtoMapper.toCreateConnectionResponse(connection); + CreateConnectionResponse createConnectionSuccessfulResponse = + connectionDtoMapper.toCreateConnectionResponse(connection); - assertEquals(randomUUID, createConnectionSuccessfulResponse.id()); - assertEquals("agentName", createConnectionSuccessfulResponse.serviceName()); - assertEquals("gepardVersion", createConnectionSuccessfulResponse.gepardVersion()); - assertEquals("otelVersion", createConnectionSuccessfulResponse.otelVersion()); - assertEquals(123L, createConnectionSuccessfulResponse.pid()); - assertEquals(1719394483600L, createConnectionSuccessfulResponse.startTime()); - assertEquals("javaVersion", createConnectionSuccessfulResponse.javaVersion()); - } -} \ No newline at end of file + assertEquals(randomUUID, createConnectionSuccessfulResponse.id()); + assertEquals("agentName", createConnectionSuccessfulResponse.serviceName()); + assertEquals("gepardVersion", createConnectionSuccessfulResponse.gepardVersion()); + assertEquals("otelVersion", createConnectionSuccessfulResponse.otelVersion()); + assertEquals(123L, createConnectionSuccessfulResponse.pid()); + assertEquals(1719394483600L, createConnectionSuccessfulResponse.startTime()); + assertEquals("javaVersion", createConnectionSuccessfulResponse.javaVersion()); + } +} From 089780cd631244cb85c9117361f18c4e949465f2 Mon Sep 17 00:00:00 2001 From: Levin Kerschberger Date: Tue, 27 Aug 2024 08:18:34 +0200 Subject: [PATCH 04/13] Refactor: Frontend - Changed Endpoints --- frontend/src/Api.js | 4 ++-- frontend/src/components/AgentDetailHeader.vue | 10 ++++++---- frontend/src/components/AgentList.vue | 20 +++++++++---------- frontend/src/components/AgentSummary.vue | 10 +++++----- .../src/pages/agents/{[name].vue => [id].vue} | 6 +++--- frontend/src/stores/Agentstore.js | 4 ++-- 6 files changed, 28 insertions(+), 26 deletions(-) rename frontend/src/pages/agents/{[name].vue => [id].vue} (90%) diff --git a/frontend/src/Api.js b/frontend/src/Api.js index 601376ce..11a0eb9d 100644 --- a/frontend/src/Api.js +++ b/frontend/src/Api.js @@ -1,6 +1,6 @@ import axios from 'axios' -const SERVER_URL = 'http://localhost:8080/api/v1/'; +const SERVER_URL = 'https://localhost:8080/api/v1/'; const instance = axios.create({ baseURL: SERVER_URL, @@ -11,7 +11,7 @@ export default { // (C)reate // createNew: (text, completed) => instance.post('agents', {title: text, completed: completed}), // (R)ead - getAll: () => instance.get('agents', { + getAll: () => instance.get('connections', { transformResponse: [function (data) { return data? JSON.parse(response.data) : data; }] diff --git a/frontend/src/components/AgentDetailHeader.vue b/frontend/src/components/AgentDetailHeader.vue index 2a1f3cce..45962866 100644 --- a/frontend/src/components/AgentDetailHeader.vue +++ b/frontend/src/components/AgentDetailHeader.vue @@ -6,7 +6,7 @@

Agent
-
{{ agent.name }}
+
{{ agent.serviceName }}

@@ -29,13 +29,15 @@ export default { }, setup() { const route = useRoute(); - const agentName = route.params.name; + const agentId = route.params.id; const agentsStore = useAgentsStore(); - const agent = agentsStore.getAgentByName(agentName); + console.log(agentId); + + const agent = agentsStore.getAgentById(agentId) return { - agentName, + agentId, agent, }; }, diff --git a/frontend/src/components/AgentList.vue b/frontend/src/components/AgentList.vue index 484f708f..da3a11b8 100644 --- a/frontend/src/components/AgentList.vue +++ b/frontend/src/components/AgentList.vue @@ -25,9 +25,9 @@ - + {{ - agent.name + agent.serviceName }} @@ -36,13 +36,13 @@ }} {{ - agent.javaversion + agent.javaVersion }} - {{ agent.otelversion }} + {{ agent.otelVersion }} - Details > + Details > @@ -83,7 +83,7 @@ export default { store.clear() try { - const response = await fetch('http://localhost:8080/api/v1/agents'); + const response = await fetch('https://localhost:8080/api/v1/connections'); const data = await response.json(); data.forEach(agent => { store.addAgent(agent); @@ -93,11 +93,11 @@ export default { } this.agents = store.agents.map(agent => ({ - // id: agent.id, - name: agent.name, + id: agent.id, + serviceName: agent.serviceName, health: agent.healthState !== null ? agent.healthState : 'missed', - javaversion: agent.javaversion, - otelversion: agent.otelversion + javaVersion: agent.javaVersion, + otelVersion: agent.otelVersion })); }, }, diff --git a/frontend/src/components/AgentSummary.vue b/frontend/src/components/AgentSummary.vue index 3a3bc7ef..4bf9a2c5 100644 --- a/frontend/src/components/AgentSummary.vue +++ b/frontend/src/components/AgentSummary.vue @@ -19,7 +19,7 @@ Otelversion: -
{{ agent.otelversion }}
+
{{ agent.otelVersion }}
@@ -28,7 +28,7 @@
- +
@@ -54,9 +54,9 @@ export default { setup() { const route = useRoute(); - const agentName = route.params.name; + const agentId = route.params.id; const agentsStore = useAgentsStore(); - const agent = agentsStore.getAgentByName(agentName); + const agent = agentsStore.getAgentById(agentId); const environments = { Dev: 'text-gray-400 bg-gray-400/10 ring-gray-400/20', @@ -70,7 +70,7 @@ export default { const deployment = agent ? (agent.deployment || defaultDeployment) : defaultDeployment; return { - agentName, + agentId, agent, environments, deployment, diff --git a/frontend/src/pages/agents/[name].vue b/frontend/src/pages/agents/[id].vue similarity index 90% rename from frontend/src/pages/agents/[name].vue rename to frontend/src/pages/agents/[id].vue index d8a42df3..3447b799 100644 --- a/frontend/src/pages/agents/[name].vue +++ b/frontend/src/pages/agents/[id].vue @@ -35,13 +35,13 @@ export default { }, setup() { const route = useRoute(); - const agentName = route.params.name; + const agentId = route.params.id; const agentsStore = useAgentsStore(); - const agent = agentsStore.getAgentByName(agentName); + const agent = agentsStore.getAgentById(agentId); return { - agentName, + agentId, agent, }; }, diff --git a/frontend/src/stores/Agentstore.js b/frontend/src/stores/Agentstore.js index 22bff66e..d4d51fe9 100644 --- a/frontend/src/stores/Agentstore.js +++ b/frontend/src/stores/Agentstore.js @@ -13,8 +13,8 @@ export const useAgentsStore = defineStore({ clear() { this.agents = [] }, - getAgentByName(name) { - return this.agents.find(agent => agent.name === name); + getAgentById(id) { + return this.agents.find(agent => agent.id === id); }, } }); From 9a4a641caf8792313ad2a9d547843b0a7ed87a4e Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Tue, 27 Aug 2024 08:51:45 +0200 Subject: [PATCH 05/13] Chore: Get CORS configuration from application.yaml --- .../application/config/CorsConfiguration.java | 14 +++++++++++--- backend/src/main/resources/application.yaml | 11 ++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java index 950034b5..b93065c8 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java @@ -1,6 +1,7 @@ /* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.application.config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -9,15 +10,22 @@ @Configuration public class CorsConfiguration implements WebMvcConfigurer { - // TODO: Get CORS config from application.yaml + @Value("${inspectit.gepard.security.cors.path-pattern}") + private String pathPattern; + + @Value("${inspectit.gepard.security.cors.allowed-origins}") + private String allowedOrigins; + + @Value("${inspectit.gepard.security.cors.allowed-methods}") + private String allowedMethods; /** * Adds CORS mappings to the registry. Allow all origins and methods. * - * @param registry + * @param registry The CorsRegistry to add mappings and allowed headers, methods and origins to. */ @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**").allowedOrigins("*").allowedMethods("*"); + registry.addMapping(pathPattern).allowedOrigins(allowedOrigins).allowedMethods(allowedMethods); } } diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index 049eb173..9a918fe3 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -11,8 +11,17 @@ spring: location: "classpath:ssl/igc-dev.p12" password: "password" type: "PKCS12" + server: port: 8080 ssl: bundle: "server" - enabled-protocols: "TLSv1.3" \ No newline at end of file + enabled-protocols: "TLSv1.3" + +inspectit: + gepard: + security: + cors: + path-pattern: "/**" + allowed-origins: "*" + allowed-methods: "*" \ No newline at end of file From 1fb0262eaea19fb0d58bc2241bf0d09b0d927a0e Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Tue, 27 Aug 2024 12:09:17 +0200 Subject: [PATCH 06/13] chore!: Refactor DTO handling and change response handling when registering/connecting an agent and implement tests --- .../controller/ConnectionController.java | 19 ++-- .../connection/model/ConnectionDtoMapper.java | 57 ---------- .../connection/model/dto/ConnectionDto.java | 37 ------- .../model/dto/CreateConnectionRequest.java | 24 +++- .../model/dto/CreateConnectionResponse.java | 33 ++++-- .../connection/service/ConnectionService.java | 29 ++--- .../agentmanager/exception/ApiError.java | 9 +- .../controller/ConnectionControllerTest.java | 22 +++- .../model/ConnectionDtoMapperTest.java | 76 ------------- .../service/ConnectionServiceTest.java | 104 ++++++++++++++++++ .../exception/GlobalExceptionHandlerTest.java | 88 +++++++++++++++ http/connections.http | 4 +- 12 files changed, 282 insertions(+), 220 deletions(-) delete mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java delete mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java delete mode 100644 backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java create mode 100644 backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java create mode 100644 backend/src/test/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandlerTest.java diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java index 8b5d0abe..08a2cd9d 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java @@ -7,14 +7,14 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; import rocks.inspectit.gepard.agentmanager.connection.service.ConnectionService; /** * Controller for handling agent connection requests. Holds the POST endpoint for handling - * connection requests from agents. + * connection requests from agents and the GET endpoints for fetching all or one connection by id. */ @RestController @RequestMapping("/api/v1/connections") @@ -24,18 +24,23 @@ public class ConnectionController { private final ConnectionService connectionService; @PostMapping - public ResponseEntity connect( - @Valid @RequestBody CreateConnectionRequest connectRequest) { - return ResponseEntity.ok(connectionService.handleConnectRequest(connectRequest)); + public ResponseEntity connect(@Valid @RequestBody CreateConnectionRequest connectRequest) { + CreateConnectionResponse connectionDto = connectionService.handleConnectRequest(connectRequest); + return ResponseEntity.created( + ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{id}") + .buildAndExpand(connectionDto.id()) + .toUri()) + .build(); } @GetMapping - public ResponseEntity> getConnections() { + public ResponseEntity> getConnections() { return ResponseEntity.ok(connectionService.getConnections()); } @GetMapping("/{id}") - public ResponseEntity getConnection(@PathVariable UUID id) { + public ResponseEntity getConnection(@PathVariable UUID id) { return ResponseEntity.ok(connectionService.getConnection(id)); } } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java deleted file mode 100644 index 6056b005..00000000 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapper.java +++ /dev/null @@ -1,57 +0,0 @@ -/* (C) 2024 */ -package rocks.inspectit.gepard.agentmanager.connection.model; - -import java.time.Instant; -import java.util.UUID; -import org.springframework.stereotype.Component; -import rocks.inspectit.gepard.agentmanager.agent.model.Agent; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; - -/** - * This class is responsible for mapping Connection objects to ConnectionResponse and - * ConnectionRequest objects. It is annotated with @Component to be automatically detected by Spring - * for dependency injection. - */ -@Component -public class ConnectionDtoMapper { - - /** - * Maps a Connection object to a ConnectionSuccessfulResponse object. - * - * @param connection The Connection object to be mapped. - * @return A ConnectionSuccessfulResponse object containing the mapped data from the Connection - * object. - */ - public CreateConnectionResponse toCreateConnectionResponse(Connection connection) { - return new CreateConnectionResponse( - connection.getId(), - connection.getAgent().getServiceName(), - connection.getAgent().getGepardVersion(), - connection.getAgent().getOtelVersion(), - connection.getAgent().getPid(), - connection.getAgent().getStartTime().toEpochMilli(), - connection.getAgent().getJavaVersion()); - } - - /** - * Maps a CreateConnectionRequest object to a Connection object. - * - * @param request The CreateConnectionRequest object to be mapped. - * @return A Connection object containing the mapped data from the CreateConnectionRequest object. - */ - public Connection toConnection(CreateConnectionRequest request) { - return Connection.builder() - .id(UUID.randomUUID()) - .agent( - Agent.builder() - .serviceName(request.serviceName()) - .gepardVersion(request.gepardVersion()) - .otelVersion(request.otelVersion()) - .pid(request.pid()) - .startTime(Instant.ofEpochMilli(request.startTime())) - .javaVersion(request.javaVersion()) - .build()) - .build(); - } -} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java deleted file mode 100644 index d736e953..00000000 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java +++ /dev/null @@ -1,37 +0,0 @@ -/* (C) 2024 */ -package rocks.inspectit.gepard.agentmanager.connection.model.dto; - -import java.util.UUID; -import rocks.inspectit.gepard.agentmanager.connection.model.Connection; - -/** - * Represents a connection response for the UI, when reading connections. - * - * @param id - * @param serviceName - * @param gepardVersion - * @param otelVersion - * @param pid - * @param startTime - * @param javaVersion - */ -public record ConnectionDto( - UUID id, - String serviceName, - String gepardVersion, - String otelVersion, - Long pid, - Long startTime, - String javaVersion) { - - public static ConnectionDto fromConnection(Connection connection) { - return new ConnectionDto( - connection.getId(), - connection.getAgent().getServiceName(), - connection.getAgent().getGepardVersion(), - connection.getAgent().getOtelVersion(), - connection.getAgent().getPid(), - connection.getAgent().getStartTime().toEpochMilli(), - connection.getAgent().getJavaVersion()); - } -} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java index e6a3adec..5a0de980 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java @@ -2,7 +2,12 @@ package rocks.inspectit.gepard.agentmanager.connection.model.dto; import jakarta.validation.constraints.NotNull; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.Builder; +import rocks.inspectit.gepard.agentmanager.agent.model.Agent; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; /** Represents a connection request from an agent. */ @Builder @@ -12,4 +17,21 @@ public record CreateConnectionRequest( @NotNull(message = "Open-Telemetry Version missing.") String otelVersion, @NotNull(message = "Process ID is missing.") Long pid, @NotNull(message = "Start-Time missing.") Long startTime, - @NotNull(message = "Java Version missing.") String javaVersion) {} + @NotNull(message = "Java Version missing.") String javaVersion) { + + public static Connection toConnection(CreateConnectionRequest createConnectionRequest) { + return Connection.builder() + .id(UUID.randomUUID()) + .registrationTime(LocalDateTime.now()) + .agent( + Agent.builder() + .gepardVersion(createConnectionRequest.gepardVersion) + .javaVersion(createConnectionRequest.javaVersion) + .otelVersion(createConnectionRequest.otelVersion) + .pid(createConnectionRequest.pid) + .serviceName(createConnectionRequest.serviceName) + .startTime(Instant.ofEpochMilli(createConnectionRequest.startTime)) + .build()) + .build(); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java index d64a383b..211ad563 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java @@ -1,24 +1,33 @@ /* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.connection.model.dto; +import java.time.LocalDateTime; import java.util.UUID; +import lombok.Builder; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; -/** - * Represents a successful connection response. - * - * @param id - * @param serviceName - * @param gepardVersion - * @param otelVersion - * @param pid - * @param startTime - * @param javaVersion - */ +/** Represents a connection response. */ +@Builder public record CreateConnectionResponse( UUID id, + LocalDateTime registrationTime, String serviceName, String gepardVersion, String otelVersion, Long pid, Long startTime, - String javaVersion) {} + String javaVersion) { + + public static CreateConnectionResponse fromConnection(Connection connection) { + return CreateConnectionResponse.builder() + .id(connection.getId()) + .registrationTime(connection.getRegistrationTime()) + .serviceName(connection.getAgent().getServiceName()) + .gepardVersion(connection.getAgent().getGepardVersion()) + .otelVersion(connection.getAgent().getOtelVersion()) + .pid(connection.getAgent().getPid()) + .startTime(connection.getAgent().getStartTime().toEpochMilli()) + .javaVersion(connection.getAgent().getJavaVersion()) + .build(); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java index e5847480..817ef28e 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java @@ -9,8 +9,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import rocks.inspectit.gepard.agentmanager.connection.model.Connection; -import rocks.inspectit.gepard.agentmanager.connection.model.ConnectionDtoMapper; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; @@ -20,37 +18,40 @@ @RequiredArgsConstructor public class ConnectionService { - private final ConnectionDtoMapper connectionDtoMapper; - private final HashMap connectionCache = new HashMap<>(); /** * Handles a connection request from an agent. * - * @param connectRequest - * @return + * @param connectRequest The request for the new connection to be created. + * @return CreateConnectionResponse The response containing all saved information. */ public CreateConnectionResponse handleConnectRequest(CreateConnectionRequest connectRequest) { - Connection connection = connectionDtoMapper.toConnection(connectRequest); + Connection connection = CreateConnectionRequest.toConnection(connectRequest); connectionCache.put(connection.getId(), connection); - return connectionDtoMapper.toCreateConnectionResponse(connection); + return CreateConnectionResponse.fromConnection(connection); } - public List getConnections() { - return connectionCache.values().stream().map(ConnectionDto::fromConnection).toList(); + /** + * Returns all connections in the cache. + * + * @return List All connections from the cache. + */ + public List getConnections() { + return connectionCache.values().stream().map(CreateConnectionResponse::fromConnection).toList(); } /** - * Returns a connection by its id. + * Returns a connection from the cache by its id. * * @param id The id of the connection. - * @return ReadConnectionDTO The connection. + * @return ConnectionDto The connection. */ - public ConnectionDto getConnection(UUID id) { + public CreateConnectionResponse getConnection(UUID id) { if (!connectionCache.containsKey(id)) { throw new NoSuchElementException("No connection with id " + id + " found in cache."); } - return ConnectionDto.fromConnection(connectionCache.get(id)); + return CreateConnectionResponse.fromConnection(connectionCache.get(id)); } } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java index 9b7d0abc..858cc036 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/exception/ApiError.java @@ -4,12 +4,5 @@ import java.time.LocalDateTime; import java.util.List; -/** - * Represents an error that occurred during an API request. - * - * @param path - * @param errors - * @param statusCode - * @param timestamp - */ +/** Represents an error that occurred during an API request. */ public record ApiError(String path, List errors, int statusCode, LocalDateTime timestamp) {} diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java index 55e200b7..2fc55e41 100644 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java @@ -4,10 +4,10 @@ import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -15,8 +15,9 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; import rocks.inspectit.gepard.agentmanager.connection.service.ConnectionService; @WebMvcTest(controllers = ConnectionController.class) @@ -58,12 +59,20 @@ void connect_whenEverythingIsValid_shouldReturnOk() throws Exception { .javaVersion("11.0.12") .build(); + Connection connection = CreateConnectionRequest.toConnection(createConnectionRequest); + when(connectionService.handleConnectRequest(createConnectionRequest)) + .thenReturn(CreateConnectionResponse.fromConnection(connection)); + mockMvc .perform( post("/api/v1/connections") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(createConnectionRequest))) - .andExpect(status().isOk()); + .andExpect(status().isCreated()) + .andExpect(header().exists("Location")) + .andExpect( + header() + .string("Location", "http://localhost/api/v1/connections/" + connection.getId())); } @Test @@ -77,8 +86,9 @@ void get_connections_whenEverythingIsValid_shouldReturnOk() throws Exception { @Test void get_connection_whenEverythingIsValid_shouldReturnOk() throws Exception { UUID uuid = UUID.randomUUID(); - ConnectionDto connection = - new ConnectionDto(uuid, "service name", "5", "7", 42L, 123456789L, "22"); + CreateConnectionResponse connection = + new CreateConnectionResponse( + uuid, LocalDateTime.now(), "service name", "5", "7", 42L, 123456789L, "22"); when(connectionService.getConnection(uuid)).thenReturn(connection); mockMvc diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java deleted file mode 100644 index 7827752a..00000000 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/model/ConnectionDtoMapperTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* (C) 2024 */ -package rocks.inspectit.gepard.agentmanager.connection.model; - -import static org.junit.jupiter.api.Assertions.*; - -import java.time.Instant; -import java.util.UUID; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import rocks.inspectit.gepard.agentmanager.agent.model.Agent; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; - -class ConnectionDtoMapperTest { - - private ConnectionDtoMapper connectionDtoMapper; - - @BeforeEach - public void setUp() { - connectionDtoMapper = new ConnectionDtoMapper(); - } - - @Test - void testToConnection() { - - // - CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .serviceName("agentName") - .gepardVersion("gepardVersion") - .otelVersion("otelVersion") - .pid(123L) - .startTime(1719394483600L) - .javaVersion("javaVersion") - .build(); - - Connection connection = connectionDtoMapper.toConnection(createConnectionRequest); - - assertEquals("agentName", connection.getAgent().getServiceName()); - assertEquals("gepardVersion", connection.getAgent().getGepardVersion()); - assertEquals("otelVersion", connection.getAgent().getOtelVersion()); - assertEquals(123L, connection.getAgent().getPid()); - assertEquals(1719394483600L, connection.getAgent().getStartTime().toEpochMilli()); - assertEquals("javaVersion", connection.getAgent().getJavaVersion()); - } - - @Test - void testToConnectionSuccessfulResponse() { - UUID randomUUID = UUID.randomUUID(); - - Connection connection = - Connection.builder() - .id(randomUUID) - .agent( - Agent.builder() - .serviceName("agentName") - .gepardVersion("gepardVersion") - .otelVersion("otelVersion") - .pid(123L) - .startTime(Instant.ofEpochMilli(1719394483600L)) - .javaVersion("javaVersion") - .build()) - .build(); - - CreateConnectionResponse createConnectionSuccessfulResponse = - connectionDtoMapper.toCreateConnectionResponse(connection); - - assertEquals(randomUUID, createConnectionSuccessfulResponse.id()); - assertEquals("agentName", createConnectionSuccessfulResponse.serviceName()); - assertEquals("gepardVersion", createConnectionSuccessfulResponse.gepardVersion()); - assertEquals("otelVersion", createConnectionSuccessfulResponse.otelVersion()); - assertEquals(123L, createConnectionSuccessfulResponse.pid()); - assertEquals(1719394483600L, createConnectionSuccessfulResponse.startTime()); - assertEquals("javaVersion", createConnectionSuccessfulResponse.javaVersion()); - } -} diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java new file mode 100644 index 00000000..75fbecc5 --- /dev/null +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java @@ -0,0 +1,104 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.connection.service; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; + +@ExtendWith(MockitoExtension.class) +class ConnectionServiceTest { + + @InjectMocks private ConnectionService connectionService; + + @Test + void testHandleConnectRequest() { + CreateConnectionRequest createConnectionRequest = + CreateConnectionRequest.builder() + .startTime(Instant.now().toEpochMilli()) + .javaVersion("22") + .otelVersion("3") + .gepardVersion("4") + .pid(4435L) + .serviceName("ServiceName") + .build(); + + CreateConnectionResponse response = + connectionService.handleConnectRequest(createConnectionRequest); + + assertEquals(createConnectionRequest.startTime(), response.startTime()); + assertEquals(createConnectionRequest.javaVersion(), response.javaVersion()); + assertEquals(createConnectionRequest.otelVersion(), response.otelVersion()); + assertEquals(createConnectionRequest.gepardVersion(), response.gepardVersion()); + assertEquals(createConnectionRequest.pid(), response.pid()); + assertEquals(createConnectionRequest.serviceName(), response.serviceName()); + assertNotNull(response.id()); + } + + @Test + void testGetConnections() { + CreateConnectionRequest createConnectionRequest = + CreateConnectionRequest.builder() + .startTime(Instant.now().toEpochMilli()) + .javaVersion("22") + .otelVersion("3") + .gepardVersion("4") + .pid(4435L) + .serviceName("ServiceName") + .build(); + connectionService.handleConnectRequest(createConnectionRequest); + connectionService.handleConnectRequest(createConnectionRequest); + + List response = connectionService.getConnections(); + + assertEquals(2, response.size()); + } + + @Test + void testGetConnectionsEmptyResult() { + List response = connectionService.getConnections(); + assertEquals(0, response.size()); + } + + @Test + void testGetConnection() { + CreateConnectionRequest createConnectionRequest = + CreateConnectionRequest.builder() + .startTime(Instant.now().toEpochMilli()) + .javaVersion("22") + .otelVersion("3") + .gepardVersion("4") + .pid(4435L) + .serviceName("ServiceName") + .build(); + CreateConnectionResponse createConnectionResponse = + connectionService.handleConnectRequest(createConnectionRequest); + + CreateConnectionResponse response = + connectionService.getConnection(createConnectionResponse.id()); + + assertEquals(createConnectionResponse.id(), response.id()); + assertEquals(createConnectionRequest.startTime(), response.startTime()); + assertEquals(createConnectionRequest.javaVersion(), response.javaVersion()); + assertEquals(createConnectionRequest.otelVersion(), response.otelVersion()); + assertEquals(createConnectionRequest.gepardVersion(), response.gepardVersion()); + assertEquals(createConnectionRequest.pid(), response.pid()); + assertEquals(createConnectionRequest.serviceName(), response.serviceName()); + } + + @Test + void testGetConnectionNotFound() { + UUID id = UUID.randomUUID(); + Exception exception = + assertThrows(NoSuchElementException.class, () -> connectionService.getConnection(id)); + assertEquals("No connection with id " + id + " found in cache.", exception.getMessage()); + } +} diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandlerTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandlerTest.java new file mode 100644 index 00000000..bbc29ccc --- /dev/null +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/exception/GlobalExceptionHandlerTest.java @@ -0,0 +1,88 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.exception; + +import static org.junit.jupiter.api.Assertions.*; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +class GlobalExceptionHandlerTest { + + private final GlobalExceptionHandler globalExceptionHandler = new GlobalExceptionHandler(); + + @Test + void handleValidationErrors() { + MethodArgumentNotValidException exception = Mockito.mock(MethodArgumentNotValidException.class); + BindingResult bindingResult = Mockito.mock(BindingResult.class); + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRequestURI()).thenReturn("requestURI"); + Mockito.when(exception.getBindingResult()).thenReturn(bindingResult); + + Mockito.when(bindingResult.getFieldErrors()) + .thenReturn(List.of(new FieldError("requestURI", "fieldError", "field error"))); + + ResponseEntity response = + globalExceptionHandler.handleValidationErrors(exception, httpServletRequest); + + assertEquals(List.of("field error"), Objects.requireNonNull(response.getBody()).errors()); + assertEquals("requestURI", response.getBody().path()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + @Test + void handleBadRequestError() { + HttpMessageNotReadableException exception = Mockito.mock(HttpMessageNotReadableException.class); + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRequestURI()).thenReturn("requestURI"); + Mockito.when(exception.getMessage()).thenReturn("exception message"); + + ResponseEntity response = + globalExceptionHandler.handleBadRequestError(exception, httpServletRequest); + + assertEquals(List.of("exception message"), Objects.requireNonNull(response.getBody()).errors()); + assertEquals("requestURI", response.getBody().path()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + @Test + void handleMethodArgumentTypeMismatch() { + MethodArgumentTypeMismatchException exception = + Mockito.mock(MethodArgumentTypeMismatchException.class); + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRequestURI()).thenReturn("requestURI"); + Mockito.when(exception.getMessage()).thenReturn("exception message"); + + ResponseEntity response = + globalExceptionHandler.handleMethodArgumentTypeMismatch(exception, httpServletRequest); + + assertEquals(List.of("exception message"), Objects.requireNonNull(response.getBody()).errors()); + assertEquals("requestURI", response.getBody().path()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + @Test + void handleNotFoundError() { + NoSuchElementException exception = Mockito.mock(NoSuchElementException.class); + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRequestURI()).thenReturn("requestURI"); + Mockito.when(exception.getMessage()).thenReturn("exception message"); + + ResponseEntity response = + globalExceptionHandler.handleNotFoundError(exception, httpServletRequest); + + assertEquals(List.of("exception message"), Objects.requireNonNull(response.getBody()).errors()); + assertEquals("requestURI", response.getBody().path()); + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } +} diff --git a/http/connections.http b/http/connections.http index 78772d47..9c36d779 100644 --- a/http/connections.http +++ b/http/connections.http @@ -34,8 +34,8 @@ Content-Type: application/json "pid": 432423 } -> {% client.global.set("agent_id", response.body.id); %} +> {% client.global.set("response_redirect", response.headers.valueOf('Location')); %} ### GET request to connections/{id} -GET https://localhost:8080/api/v1/connections/{{agent_id}} +GET {{response_redirect}} From 1d88be3b6ea4eb7f2ae77773044a6056c2434795 Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Tue, 27 Aug 2024 13:28:24 +0200 Subject: [PATCH 07/13] chore: Refactor DTO namings and adjust tests --- .../controller/ConnectionController.java | 11 ++--- ...ectionResponse.java => ConnectionDto.java} | 6 +-- .../connection/service/ConnectionService.java | 18 ++++---- .../controller/ConnectionControllerTest.java | 13 +++--- .../service/ConnectionServiceTest.java | 45 +++++++++---------- 5 files changed, 46 insertions(+), 47 deletions(-) rename backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/{CreateConnectionResponse.java => ConnectionDto.java} (85%) diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java index 08a2cd9d..4c6bed35 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java @@ -8,8 +8,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; import rocks.inspectit.gepard.agentmanager.connection.service.ConnectionService; /** @@ -25,22 +26,22 @@ public class ConnectionController { @PostMapping public ResponseEntity connect(@Valid @RequestBody CreateConnectionRequest connectRequest) { - CreateConnectionResponse connectionDto = connectionService.handleConnectRequest(connectRequest); + Connection connection = connectionService.handleConnectRequest(connectRequest); return ResponseEntity.created( ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}") - .buildAndExpand(connectionDto.id()) + .buildAndExpand(connection.getId()) .toUri()) .build(); } @GetMapping - public ResponseEntity> getConnections() { + public ResponseEntity> getConnections() { return ResponseEntity.ok(connectionService.getConnections()); } @GetMapping("/{id}") - public ResponseEntity getConnection(@PathVariable UUID id) { + public ResponseEntity getConnection(@PathVariable UUID id) { return ResponseEntity.ok(connectionService.getConnection(id)); } } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java similarity index 85% rename from backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java rename to backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java index 211ad563..cb9da089 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionResponse.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java @@ -8,7 +8,7 @@ /** Represents a connection response. */ @Builder -public record CreateConnectionResponse( +public record ConnectionDto( UUID id, LocalDateTime registrationTime, String serviceName, @@ -18,8 +18,8 @@ public record CreateConnectionResponse( Long startTime, String javaVersion) { - public static CreateConnectionResponse fromConnection(Connection connection) { - return CreateConnectionResponse.builder() + public static ConnectionDto fromConnection(Connection connection) { + return ConnectionDto.builder() .id(connection.getId()) .registrationTime(connection.getRegistrationTime()) .serviceName(connection.getAgent().getServiceName()) diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java index 817ef28e..15b1e2a3 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java @@ -9,8 +9,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import rocks.inspectit.gepard.agentmanager.connection.model.Connection; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; /** Service-Implementation for handling agent connection requests. */ @Slf4j @@ -24,22 +24,22 @@ public class ConnectionService { * Handles a connection request from an agent. * * @param connectRequest The request for the new connection to be created. - * @return CreateConnectionResponse The response containing all saved information. + * @return Connection The response containing all saved information. */ - public CreateConnectionResponse handleConnectRequest(CreateConnectionRequest connectRequest) { + public Connection handleConnectRequest(CreateConnectionRequest connectRequest) { Connection connection = CreateConnectionRequest.toConnection(connectRequest); connectionCache.put(connection.getId(), connection); - return CreateConnectionResponse.fromConnection(connection); + return connection; } /** * Returns all connections in the cache. * - * @return List All connections from the cache. + * @return List All connections from the cache. */ - public List getConnections() { - return connectionCache.values().stream().map(CreateConnectionResponse::fromConnection).toList(); + public List getConnections() { + return connectionCache.values().stream().map(ConnectionDto::fromConnection).toList(); } /** @@ -48,10 +48,10 @@ public List getConnections() { * @param id The id of the connection. * @return ConnectionDto The connection. */ - public CreateConnectionResponse getConnection(UUID id) { + public ConnectionDto getConnection(UUID id) { if (!connectionCache.containsKey(id)) { throw new NoSuchElementException("No connection with id " + id + " found in cache."); } - return CreateConnectionResponse.fromConnection(connectionCache.get(id)); + return ConnectionDto.fromConnection(connectionCache.get(id)); } } diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java index 2fc55e41..b23c515f 100644 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java @@ -16,8 +16,8 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import rocks.inspectit.gepard.agentmanager.connection.model.Connection; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; import rocks.inspectit.gepard.agentmanager.connection.service.ConnectionService; @WebMvcTest(controllers = ConnectionController.class) @@ -60,8 +60,7 @@ void connect_whenEverythingIsValid_shouldReturnOk() throws Exception { .build(); Connection connection = CreateConnectionRequest.toConnection(createConnectionRequest); - when(connectionService.handleConnectRequest(createConnectionRequest)) - .thenReturn(CreateConnectionResponse.fromConnection(connection)); + when(connectionService.handleConnectRequest(createConnectionRequest)).thenReturn(connection); mockMvc .perform( @@ -86,15 +85,15 @@ void get_connections_whenEverythingIsValid_shouldReturnOk() throws Exception { @Test void get_connection_whenEverythingIsValid_shouldReturnOk() throws Exception { UUID uuid = UUID.randomUUID(); - CreateConnectionResponse connection = - new CreateConnectionResponse( + ConnectionDto connectionDto = + new ConnectionDto( uuid, LocalDateTime.now(), "service name", "5", "7", 42L, 123456789L, "22"); - when(connectionService.getConnection(uuid)).thenReturn(connection); + when(connectionService.getConnection(uuid)).thenReturn(connectionDto); mockMvc .perform(get("/api/v1/connections/{id}", uuid)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(objectMapper.writeValueAsString(connection))); + .andExpect(content().json(objectMapper.writeValueAsString(connectionDto))); } } diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java index 75fbecc5..307ecd1a 100644 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java @@ -11,8 +11,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.gepard.agentmanager.connection.model.Connection; +import rocks.inspectit.gepard.agentmanager.connection.model.dto.ConnectionDto; import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionRequest; -import rocks.inspectit.gepard.agentmanager.connection.model.dto.CreateConnectionResponse; @ExtendWith(MockitoExtension.class) class ConnectionServiceTest { @@ -31,16 +32,16 @@ void testHandleConnectRequest() { .serviceName("ServiceName") .build(); - CreateConnectionResponse response = - connectionService.handleConnectRequest(createConnectionRequest); + Connection response = connectionService.handleConnectRequest(createConnectionRequest); - assertEquals(createConnectionRequest.startTime(), response.startTime()); - assertEquals(createConnectionRequest.javaVersion(), response.javaVersion()); - assertEquals(createConnectionRequest.otelVersion(), response.otelVersion()); - assertEquals(createConnectionRequest.gepardVersion(), response.gepardVersion()); - assertEquals(createConnectionRequest.pid(), response.pid()); - assertEquals(createConnectionRequest.serviceName(), response.serviceName()); - assertNotNull(response.id()); + assertEquals( + createConnectionRequest.startTime(), response.getAgent().getStartTime().toEpochMilli()); + assertEquals(createConnectionRequest.javaVersion(), response.getAgent().getJavaVersion()); + assertEquals(createConnectionRequest.otelVersion(), response.getAgent().getOtelVersion()); + assertEquals(createConnectionRequest.gepardVersion(), response.getAgent().getGepardVersion()); + assertEquals(createConnectionRequest.pid(), response.getAgent().getPid()); + assertEquals(createConnectionRequest.serviceName(), response.getAgent().getServiceName()); + assertNotNull(response.getId()); } @Test @@ -57,14 +58,14 @@ void testGetConnections() { connectionService.handleConnectRequest(createConnectionRequest); connectionService.handleConnectRequest(createConnectionRequest); - List response = connectionService.getConnections(); + List response = connectionService.getConnections(); assertEquals(2, response.size()); } @Test void testGetConnectionsEmptyResult() { - List response = connectionService.getConnections(); + List response = connectionService.getConnections(); assertEquals(0, response.size()); } @@ -79,19 +80,17 @@ void testGetConnection() { .pid(4435L) .serviceName("ServiceName") .build(); - CreateConnectionResponse createConnectionResponse = - connectionService.handleConnectRequest(createConnectionRequest); + Connection connection = connectionService.handleConnectRequest(createConnectionRequest); - CreateConnectionResponse response = - connectionService.getConnection(createConnectionResponse.id()); + ConnectionDto connectionDto = connectionService.getConnection(connection.getId()); - assertEquals(createConnectionResponse.id(), response.id()); - assertEquals(createConnectionRequest.startTime(), response.startTime()); - assertEquals(createConnectionRequest.javaVersion(), response.javaVersion()); - assertEquals(createConnectionRequest.otelVersion(), response.otelVersion()); - assertEquals(createConnectionRequest.gepardVersion(), response.gepardVersion()); - assertEquals(createConnectionRequest.pid(), response.pid()); - assertEquals(createConnectionRequest.serviceName(), response.serviceName()); + assertEquals(connection.getId(), connectionDto.id()); + assertEquals(createConnectionRequest.startTime(), connectionDto.startTime()); + assertEquals(createConnectionRequest.javaVersion(), connectionDto.javaVersion()); + assertEquals(createConnectionRequest.otelVersion(), connectionDto.otelVersion()); + assertEquals(createConnectionRequest.gepardVersion(), connectionDto.gepardVersion()); + assertEquals(createConnectionRequest.pid(), connectionDto.pid()); + assertEquals(createConnectionRequest.serviceName(), connectionDto.serviceName()); } @Test From 533c4c464cab127c4cb63ae902ee0f403b0b619f Mon Sep 17 00:00:00 2001 From: Levin Kerschberger Date: Tue, 27 Aug 2024 14:15:57 +0200 Subject: [PATCH 08/13] Feat: VHVAPM-530/531 --- .gitignore | 3 + backend/build.gradle | 10 +++ .../controller/ConfigurationController.java | 40 +++++++++++ .../model/InspectitConfiguration.java | 17 +++++ .../InstrumentationConfiguration.java | 20 ++++++ .../model/instrumentation/Scope.java | 24 +++++++ .../service/ConfigurationService.java | 19 +++++ .../controller/ConnectionController.java | 4 ++ backend/src/main/resources/application.yaml | 12 ++++ .../ConfigurationControllerTest.java | 71 +++++++++++++++++++ 10 files changed, 220 insertions(+) create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/InspectitConfiguration.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java create mode 100644 backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java create mode 100644 backend/src/test/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationControllerTest.java diff --git a/.gitignore b/.gitignore index 4620c279..2931085f 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ fabric.properties /ui/build_packageClient/ **/.DS_Store + +**/node_modules +**/.nuxt diff --git a/backend/build.gradle b/backend/build.gradle index 5703d34b..fd9fb446 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -30,8 +30,18 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-validation' annotationProcessor 'org.projectlombok:lombok' + + // Actuator - for management endpoints + implementation 'org.springframework.boot:spring-boot-starter-actuator' + // Swagger - for API documentation + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + + // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // Management + } tasks.named('test') { diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java new file mode 100644 index 00000000..0cc7b700 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java @@ -0,0 +1,40 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.configuration.controller; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import rocks.inspectit.gepard.agentmanager.configuration.model.InspectitConfiguration; +import rocks.inspectit.gepard.agentmanager.configuration.service.ConfigurationService; + +@RestController +@RequestMapping("/api/v1/agent-configuration") +@RequiredArgsConstructor +public class ConfigurationController { + + private final ConfigurationService configurationService; + + @GetMapping + @Operation(summary = "Get the agent configuration.") + public ResponseEntity getAgentConfiguration() { + InspectitConfiguration configuration = configurationService.getConfiguration(); + + // No config available + if (Objects.isNull(configuration)) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok().body(configurationService.getConfiguration()); + } + + @PutMapping + @Operation(summary = "Update the agent configuration.") + public ResponseEntity updateAgentConfiguration( + @Valid @RequestBody InspectitConfiguration configuration) { + configurationService.updateConfiguration(configuration); + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/InspectitConfiguration.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/InspectitConfiguration.java new file mode 100644 index 00000000..521e34d2 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/InspectitConfiguration.java @@ -0,0 +1,17 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.configuration.model; + +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import rocks.inspectit.gepard.agentmanager.configuration.model.instrumentation.InstrumentationConfiguration; + +/** Model of an inspectit gepard configuration. */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class InspectitConfiguration { + + @Valid private InstrumentationConfiguration instrumentation = new InstrumentationConfiguration(); +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java new file mode 100644 index 00000000..a2a07e4e --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java @@ -0,0 +1,20 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.configuration.model.instrumentation; + +import jakarta.validation.Valid; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * The Instrumentation Configuration contains all configuration related to instrumentation. e.g + * scopes, rules, actions. + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class InstrumentationConfiguration { + + @Valid private List scopes = List.of(); +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java new file mode 100644 index 00000000..2bbce88c --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java @@ -0,0 +1,24 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.configuration.model.instrumentation; + +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Represents a scope in the instrumentation configuration. A scope defines a set of methods which + * should be instrumented. + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class Scope { + + @NotNull(message = "Fqn is missing.") private String fqn; + + private List methods = List.of(); + + private boolean enabled = false; +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java new file mode 100644 index 00000000..3ff00ab9 --- /dev/null +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java @@ -0,0 +1,19 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.configuration.service; + +import org.springframework.stereotype.Service; +import rocks.inspectit.gepard.agentmanager.configuration.model.InspectitConfiguration; + +@Service +public class ConfigurationService { + + private InspectitConfiguration inspectitConfiguration; + + public InspectitConfiguration getConfiguration() { + return inspectitConfiguration; + } + + public void updateConfiguration(InspectitConfiguration configuration) { + inspectitConfiguration = configuration; + } +} diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java index 4c6bed35..569085c7 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionController.java @@ -1,6 +1,7 @@ /* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.connection.controller; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import java.util.List; import java.util.UUID; @@ -25,6 +26,7 @@ public class ConnectionController { private final ConnectionService connectionService; @PostMapping + @Operation(summary = "Connect an agent to the agent manager.") public ResponseEntity connect(@Valid @RequestBody CreateConnectionRequest connectRequest) { Connection connection = connectionService.handleConnectRequest(connectRequest); return ResponseEntity.created( @@ -36,11 +38,13 @@ public ResponseEntity connect(@Valid @RequestBody CreateConnectionRequest } @GetMapping + @Operation(summary = "Get all connections.") public ResponseEntity> getConnections() { return ResponseEntity.ok(connectionService.getConnections()); } @GetMapping("/{id}") + @Operation(summary = "Get a connection by id.") public ResponseEntity getConnection(@PathVariable UUID id) { return ResponseEntity.ok(connectionService.getConnection(id)); } diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index 9a918fe3..d960767d 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -18,6 +18,18 @@ server: bundle: "server" enabled-protocols: "TLSv1.3" +management: + server: + port: 9090 + endpoints: + web: + exposure: + include: "openapi, swagger-ui" + +springdoc: + show-actuator: true + use-management-port: true + inspectit: gepard: security: diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationControllerTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationControllerTest.java new file mode 100644 index 00000000..dabc3b64 --- /dev/null +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationControllerTest.java @@ -0,0 +1,71 @@ +/* (C) 2024 */ +package rocks.inspectit.gepard.agentmanager.configuration.controller; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import rocks.inspectit.gepard.agentmanager.configuration.model.InspectitConfiguration; +import rocks.inspectit.gepard.agentmanager.configuration.model.instrumentation.InstrumentationConfiguration; +import rocks.inspectit.gepard.agentmanager.configuration.model.instrumentation.Scope; +import rocks.inspectit.gepard.agentmanager.configuration.service.ConfigurationService; + +@WebMvcTest(controllers = ConfigurationController.class) +class ConfigurationControllerTest { + + @Autowired private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + + @MockBean private ConfigurationService configurationService; + + @Test + void getConfiguration_whenNoConfigAvailable_shouldReturnNoContent() throws Exception { + + when(configurationService.getConfiguration()).thenReturn(null); + + mockMvc.perform(get("/api/v1/agent-configuration")).andExpect(status().isNoContent()); + } + + @Test + void getConfiguration_whenConfigAvailable_shouldReturnOk() throws Exception { + when(configurationService.getConfiguration()).thenReturn(new InspectitConfiguration()); + + mockMvc + .perform(get("/api/v1/agent-configuration")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(new InspectitConfiguration()))); + } + + @Test + void updateConfiguration_shouldReturnOkAndConfiguration() throws Exception { + + InspectitConfiguration configuration = createConfiguration(); + + when(configurationService.getConfiguration()).thenReturn(configuration); + + mockMvc + .perform( + get("/api/v1/agent-configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(configuration))) + .andExpect(status().isOk()); + } + + private InspectitConfiguration createConfiguration() { + Scope scope = new Scope("org.test.package", List.of("testMethod"), true); + InstrumentationConfiguration instrumentationConfiguration = + new InstrumentationConfiguration(List.of(scope)); + return new InspectitConfiguration(instrumentationConfiguration); + } +} From 0ef9416701df18b866b25ea1ab0f4a6fc0d174da Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Wed, 28 Aug 2024 10:29:16 +0200 Subject: [PATCH 09/13] chore: Small refactoring of unneeded getter call --- .../configuration/controller/ConfigurationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java index 0cc7b700..2c8b5e97 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/controller/ConfigurationController.java @@ -27,7 +27,7 @@ public ResponseEntity getAgentConfiguration() { return ResponseEntity.noContent().build(); } - return ResponseEntity.ok().body(configurationService.getConfiguration()); + return ResponseEntity.ok().body(configuration); } @PutMapping From a19cbe26de61aee58161d619426b2ef75d179351 Mon Sep 17 00:00:00 2001 From: Levin Kerschberger Date: Wed, 28 Aug 2024 11:22:16 +0200 Subject: [PATCH 10/13] chore: created docs --- CONTRIBUTING.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 17 +++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..650f06f1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing + +## IDE + +We recommend using [IntelliJ](https://www.jetbrains.com/idea/download/#section=windows) as IDE for contributing. + +## Formatting + +We have [spotless](https://github.com/diffplug/spotless) configured to format the code. You can run the following commands: + +- `./gradlew spotlessCheck` to validate the formatting of the code. +- `./gradlew spotlessApply` to format the code. + +Be aware that the CI will fail if the code is not formatted correctly, as `spotlessCheck` is part of the build process. + + +## Building + +### Prerequisites +Please make sure you have the following tools installed on your machine: +- A big cup of coffee +- A Java Development Kit (JDK) with version 21 or higher +- A working internet connection (for downloading dependencies) +- Docker (optional, for running the application in a container) + +Thats it for now. + +### Backend +To build the backend, simply run the following command in the root directory of the project: +```shell +./gradlew +``` +This will generate the backend jar file in the `server/build/libs` directory. + +> [!TIP] +> You might have installed an auto-formatter in your IDE. It may break the installed spotless code-style. In this case, the build will not be successfull. Please run ```./gradlew potlessApply``` to fix this issue. + +### Frontend +To build the frontend, navigate to the `frontend/src` directory and run the following commands: +```shell + npm install + npm run build +``` +This will generate an entry point that launches a ready-to-run node server in the `frontend/src/.output` directory. + +You could also run the frontend in development mode by running: +```shell + npm run dev +``` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..b5dbf32d --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Welcome to the InspectIT Gepard Config-Server +InspectIT Gepard is an Extension for the OpenTelemetry Java agent, +enhancing your application with custom monitoring capabilities. +The agent is configured via a central configuration server, +which is responsible for providing instrumentation-rules, -scopes and -action along to configuration settings to the agent. +This repository contains the implementation of the configuration servers central component, +along with a User Interface for managing agents easily. + +InspectIT Gepard is developed by Novatec Consulting GmbH and backed by VHV Group. +It´s purpose is to replace the InspectIT Ocelot project with a more modern and flexible approach, utilizing the OpenTelemetry standard. + +## Getting Started +Currently, the project is in an early stage of development and not yet ready for production use. +However, if you want to try it out, you can follow the instructions in [CONTRIBUTING](./CONTRIBUTING.md). + +## Useful Links +- [SonarCloud](https://sonarcloud.io/) \ No newline at end of file From b7651fcd86167713455e8d91f94291f60e795ee3 Mon Sep 17 00:00:00 2001 From: Levin Kerschberger Date: Wed, 28 Aug 2024 11:23:22 +0200 Subject: [PATCH 11/13] fix: removed ssl from default config --- backend/src/main/resources/application.yaml | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index d960767d..83326411 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -1,22 +1,22 @@ spring: application: name: backend - ssl: - bundle: - jks: - server: - key: - alias: "igc-dev" - keystore: - location: "classpath:ssl/igc-dev.p12" - password: "password" - type: "PKCS12" +# ssl: +# bundle: +# jks: +# server: +# key: +# alias: "igc-dev" +# keystore: +# location: "classpath:ssl/igc-dev.p12" +# password: "password" +# type: "PKCS12" server: port: 8080 - ssl: - bundle: "server" - enabled-protocols: "TLSv1.3" +# ssl: +# bundle: "server" +# enabled-protocols: "TLSv1.3" management: server: From bf183eaf049d9f1fc595b00d3faba8006b425d1b Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Thu, 29 Aug 2024 14:55:08 +0200 Subject: [PATCH 12/13] refactor: Fix findings and PR comments --- backend/build.gradle | 3 -- .../agentmanager/agent/model/Agent.java | 17 +++++------ .../application/config/CorsConfiguration.java | 6 ++-- .../InstrumentationConfiguration.java | 3 +- .../model/instrumentation/Scope.java | 2 +- .../service/ConfigurationService.java | 2 +- .../connection/model/dto/ConnectionDto.java | 17 ++++++----- .../model/dto/CreateConnectionRequest.java | 2 -- .../connection/service/ConnectionService.java | 4 +-- backend/src/main/resources/application.yaml | 13 ++++---- .../controller/ConnectionControllerTest.java | 29 ++++++++---------- .../service/ConnectionServiceTest.java | 30 ++++--------------- 12 files changed, 51 insertions(+), 77 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index fd9fb446..3df9308e 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -39,9 +39,6 @@ dependencies { // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - // Management - } tasks.named('test') { diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java index 6d6c8c14..1ea1f1b6 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java @@ -1,32 +1,31 @@ /* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.agent.model; -import jakarta.annotation.Nonnull; +import jakarta.validation.constraints.NotNull; import java.time.Instant; import lombok.*; /** Represents an agent which is connected to the config server. */ -@RequiredArgsConstructor -@NoArgsConstructor +@AllArgsConstructor @Builder @ToString @Getter public class Agent { /** The name of the service which is running the agent. */ - @Nonnull private String serviceName; + @NotNull private String serviceName; /** The process id of the JVM which carries the agent. */ - @Nonnull private Long pid; + @NotNull private Long pid; /** The Gepard-Version. */ - @Nonnull private String gepardVersion; + @NotNull private String gepardVersion; /** The OpenTelemetry-Java-Instrumentation-Version. */ - @Nonnull private String otelVersion; + @NotNull private String otelVersion; /** The start time of the JVM which carries the agent. */ - @Nonnull private Instant startTime; + @NotNull private Instant startTime; /** The Java version of the JVM which carries the agent. */ - @Nonnull private String javaVersion; + @NotNull private String javaVersion; } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java index b93065c8..7f8d2355 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/application/config/CorsConfiguration.java @@ -10,13 +10,13 @@ @Configuration public class CorsConfiguration implements WebMvcConfigurer { - @Value("${inspectit.gepard.security.cors.path-pattern}") + @Value("${inspectit-config-server.security.cors.path-pattern}") private String pathPattern; - @Value("${inspectit.gepard.security.cors.allowed-origins}") + @Value("${inspectit-config-server.security.cors.allowed-origins}") private String allowedOrigins; - @Value("${inspectit.gepard.security.cors.allowed-methods}") + @Value("${inspectit-config-server.security.cors.allowed-methods}") private String allowedMethods; /** diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java index a2a07e4e..93962f88 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/InstrumentationConfiguration.java @@ -2,6 +2,7 @@ package rocks.inspectit.gepard.agentmanager.configuration.model.instrumentation; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; @@ -16,5 +17,5 @@ @Getter public class InstrumentationConfiguration { - @Valid private List scopes = List.of(); + @Valid private List<@NotNull Scope> scopes = List.of(); } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java index 2bbce88c..ce1a6c9c 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/model/instrumentation/Scope.java @@ -18,7 +18,7 @@ public class Scope { @NotNull(message = "Fqn is missing.") private String fqn; - private List methods = List.of(); + private List<@NotNull String> methods = List.of(); private boolean enabled = false; } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java index 3ff00ab9..80436a54 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/configuration/service/ConfigurationService.java @@ -7,7 +7,7 @@ @Service public class ConfigurationService { - private InspectitConfiguration inspectitConfiguration; + private volatile InspectitConfiguration inspectitConfiguration; public InspectitConfiguration getConfiguration() { return inspectitConfiguration; diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java index cb9da089..39b25684 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java @@ -1,6 +1,7 @@ /* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.connection.model.dto; +import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.UUID; import lombok.Builder; @@ -9,14 +10,14 @@ /** Represents a connection response. */ @Builder public record ConnectionDto( - UUID id, - LocalDateTime registrationTime, - String serviceName, - String gepardVersion, - String otelVersion, - Long pid, - Long startTime, - String javaVersion) { + @NotNull(message = "ID missing.") UUID id, + @NotNull(message = "Registration Time missing.") LocalDateTime registrationTime, + @NotNull(message = "Service Name missing.") String serviceName, + @NotNull(message = "Gepard Version missing.") String gepardVersion, + @NotNull(message = "Open-Telemetry Version missing.") String otelVersion, + @NotNull(message = "Process ID is missing.") Long pid, + @NotNull(message = "Start-Time missing.") Long startTime, + @NotNull(message = "Java Version missing.") String javaVersion) { public static ConnectionDto fromConnection(Connection connection) { return ConnectionDto.builder() diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java index 5a0de980..5ee08dc6 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java @@ -5,12 +5,10 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.UUID; -import lombok.Builder; import rocks.inspectit.gepard.agentmanager.agent.model.Agent; import rocks.inspectit.gepard.agentmanager.connection.model.Connection; /** Represents a connection request from an agent. */ -@Builder public record CreateConnectionRequest( @NotNull(message = "Service Name missing.") String serviceName, @NotNull(message = "Gepard Version missing.") String gepardVersion, diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java index 15b1e2a3..697cf741 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionService.java @@ -1,10 +1,10 @@ /* (C) 2024 */ package rocks.inspectit.gepard.agentmanager.connection.service; -import java.util.HashMap; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -18,7 +18,7 @@ @RequiredArgsConstructor public class ConnectionService { - private final HashMap connectionCache = new HashMap<>(); + private final ConcurrentHashMap connectionCache = new ConcurrentHashMap<>(); /** * Handles a connection request from an agent. diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index 83326411..732f5d4c 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -30,10 +30,9 @@ springdoc: show-actuator: true use-management-port: true -inspectit: - gepard: - security: - cors: - path-pattern: "/**" - allowed-origins: "*" - allowed-methods: "*" \ No newline at end of file +inspectit-config-server: + security: + cors: + path-pattern: "/**" + allowed-origins: "*" + allowed-methods: "*" \ No newline at end of file diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java index b23c515f..a26a8da4 100644 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/controller/ConnectionControllerTest.java @@ -7,6 +7,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Instant; import java.time.LocalDateTime; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -31,33 +32,29 @@ class ConnectionControllerTest { @Test void connect_whenFieldIsMissing_shouldReturnBadRequest() throws Exception { - CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .serviceName("customer-service-e") - .pid(67887L) - .gepardVersion("0.0.1") - .otelVersion("1.26.8") - .build(); + String requestBody = + """ + { + "serviceName": "customer-service-e", + "gepardVersion: "0.0.1", + "otelVersion": "1.26.8" + + } + """; mockMvc .perform( post("/api/v1/connections") .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(createConnectionRequest))) + .content(requestBody)) .andExpect(status().isBadRequest()); } @Test void connect_whenEverythingIsValid_shouldReturnOk() throws Exception { CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .serviceName("customer-service-e") - .pid(67887L) - .gepardVersion("0.0.1") - .otelVersion("1.26.8") - .startTime(213423L) - .javaVersion("11.0.12") - .build(); + new CreateConnectionRequest( + "customer-service-e", "0.0.1", "1.26.8", 67887L, Instant.now().toEpochMilli(), "22"); Connection connection = CreateConnectionRequest.toConnection(createConnectionRequest); when(connectionService.handleConnectRequest(createConnectionRequest)).thenReturn(connection); diff --git a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java index 307ecd1a..8a029171 100644 --- a/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java +++ b/backend/src/test/java/rocks/inspectit/gepard/agentmanager/connection/service/ConnectionServiceTest.java @@ -23,14 +23,8 @@ class ConnectionServiceTest { @Test void testHandleConnectRequest() { CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .startTime(Instant.now().toEpochMilli()) - .javaVersion("22") - .otelVersion("3") - .gepardVersion("4") - .pid(4435L) - .serviceName("ServiceName") - .build(); + new CreateConnectionRequest( + "customer-service-e", "0.0.1", "1.26.8", 67887L, Instant.now().toEpochMilli(), "22"); Connection response = connectionService.handleConnectRequest(createConnectionRequest); @@ -47,14 +41,8 @@ void testHandleConnectRequest() { @Test void testGetConnections() { CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .startTime(Instant.now().toEpochMilli()) - .javaVersion("22") - .otelVersion("3") - .gepardVersion("4") - .pid(4435L) - .serviceName("ServiceName") - .build(); + new CreateConnectionRequest( + "customer-service-e", "0.0.1", "1.26.8", 67887L, Instant.now().toEpochMilli(), "22"); connectionService.handleConnectRequest(createConnectionRequest); connectionService.handleConnectRequest(createConnectionRequest); @@ -72,14 +60,8 @@ void testGetConnectionsEmptyResult() { @Test void testGetConnection() { CreateConnectionRequest createConnectionRequest = - CreateConnectionRequest.builder() - .startTime(Instant.now().toEpochMilli()) - .javaVersion("22") - .otelVersion("3") - .gepardVersion("4") - .pid(4435L) - .serviceName("ServiceName") - .build(); + new CreateConnectionRequest( + "customer-service-e", "0.0.1", "1.26.8", 67887L, Instant.now().toEpochMilli(), "22"); Connection connection = connectionService.handleConnectRequest(createConnectionRequest); ConnectionDto connectionDto = connectionService.getConnection(connection.getId()); From 8eaae219e82dc006f0224b540bb6feefe64b3db3 Mon Sep 17 00:00:00 2001 From: Benjamin Clauss Date: Thu, 29 Aug 2024 16:15:56 +0200 Subject: [PATCH 13/13] refactor: Remove @Builder since we only create a new instance once --- .../agentmanager/agent/model/Agent.java | 1 - .../connection/model/Connection.java | 1 - .../connection/model/dto/ConnectionDto.java | 21 ++++++++--------- .../model/dto/CreateConnectionRequest.java | 23 ++++++++----------- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java index 1ea1f1b6..b98a9ea0 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/agent/model/Agent.java @@ -7,7 +7,6 @@ /** Represents an agent which is connected to the config server. */ @AllArgsConstructor -@Builder @ToString @Getter public class Agent { diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java index bb43082a..3ca6b3a2 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/Connection.java @@ -10,7 +10,6 @@ * Represents a connected agent. It is an internal data structure and not exposed to the API. Acts * as Aggregate Root. */ -@Builder @AllArgsConstructor @NoArgsConstructor @Getter diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java index 39b25684..e50e6aa9 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/ConnectionDto.java @@ -4,11 +4,9 @@ import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.UUID; -import lombok.Builder; import rocks.inspectit.gepard.agentmanager.connection.model.Connection; /** Represents a connection response. */ -@Builder public record ConnectionDto( @NotNull(message = "ID missing.") UUID id, @NotNull(message = "Registration Time missing.") LocalDateTime registrationTime, @@ -20,15 +18,14 @@ public record ConnectionDto( @NotNull(message = "Java Version missing.") String javaVersion) { public static ConnectionDto fromConnection(Connection connection) { - return ConnectionDto.builder() - .id(connection.getId()) - .registrationTime(connection.getRegistrationTime()) - .serviceName(connection.getAgent().getServiceName()) - .gepardVersion(connection.getAgent().getGepardVersion()) - .otelVersion(connection.getAgent().getOtelVersion()) - .pid(connection.getAgent().getPid()) - .startTime(connection.getAgent().getStartTime().toEpochMilli()) - .javaVersion(connection.getAgent().getJavaVersion()) - .build(); + return new ConnectionDto( + connection.getId(), + connection.getRegistrationTime(), + connection.getAgent().getServiceName(), + connection.getAgent().getGepardVersion(), + connection.getAgent().getOtelVersion(), + connection.getAgent().getPid(), + connection.getAgent().getStartTime().toEpochMilli(), + connection.getAgent().getJavaVersion()); } } diff --git a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java index 5ee08dc6..b9f2f66d 100644 --- a/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java +++ b/backend/src/main/java/rocks/inspectit/gepard/agentmanager/connection/model/dto/CreateConnectionRequest.java @@ -18,18 +18,15 @@ public record CreateConnectionRequest( @NotNull(message = "Java Version missing.") String javaVersion) { public static Connection toConnection(CreateConnectionRequest createConnectionRequest) { - return Connection.builder() - .id(UUID.randomUUID()) - .registrationTime(LocalDateTime.now()) - .agent( - Agent.builder() - .gepardVersion(createConnectionRequest.gepardVersion) - .javaVersion(createConnectionRequest.javaVersion) - .otelVersion(createConnectionRequest.otelVersion) - .pid(createConnectionRequest.pid) - .serviceName(createConnectionRequest.serviceName) - .startTime(Instant.ofEpochMilli(createConnectionRequest.startTime)) - .build()) - .build(); + return new Connection( + UUID.randomUUID(), + LocalDateTime.now(), + new Agent( + createConnectionRequest.serviceName, + createConnectionRequest.pid, + createConnectionRequest.gepardVersion, + createConnectionRequest.otelVersion, + Instant.ofEpochMilli(createConnectionRequest.startTime), + createConnectionRequest.javaVersion)); } }