diff --git a/.github/workflows/app-test-charts.yaml b/.github/workflows/app-test-charts.yaml index 9e4e451de..c914bc6ff 100644 --- a/.github/workflows/app-test-charts.yaml +++ b/.github/workflows/app-test-charts.yaml @@ -59,6 +59,7 @@ on: paths: - 'pom.xml' - 'bpdm-**' + - 'charts/**' push: branches: - main diff --git a/.github/workflows/chart-test-lint.yaml b/.github/workflows/chart-test-lint.yaml index fc6d06e36..122c16170 100644 --- a/.github/workflows/chart-test-lint.yaml +++ b/.github/workflows/chart-test-lint.yaml @@ -45,6 +45,21 @@ jobs: with: fetch-depth: 0 + - name: Check for Release Version + id: checkRelease + run: | + CHART_VERSION=$(grep 'version:' ./charts/bpdm/Chart.yaml | head -n1 | awk '{ print $2}') + echo "CHART_VERSION: $CHART_VERSION" + SNAPSHOT=$(echo "$CHART_VERSION" | grep 'SNAPSHOT' || echo "" ) + echo "SNAPSHOT: $SNAPSHOT" + if [[ -z "$SNAPSHOT" ]]; then + IS_RELEASE=true + else + IS_RELEASE=false + fi + echo "IS_RELEASE: $IS_RELEASE" + echo "isRelease=$IS_RELEASE" >> $GITHUB_OUTPUT + - name: Set up Helm uses: azure/setup-helm@v4 with: @@ -57,6 +72,7 @@ jobs: run: | cat < .chart-testing-config.yaml validate-maintainers: false + check-version-increment: ${{ steps.checkRelease.outputs.isRelease }} chart-repos: - bitnami=https://charts.bitnami.com/bitnami EOF diff --git a/.github/workflows/helm-chart-release.yaml b/.github/workflows/helm-chart-release.yaml index b43f96a62..08da63393 100644 --- a/.github/workflows/helm-chart-release.yaml +++ b/.github/workflows/helm-chart-release.yaml @@ -41,8 +41,15 @@ jobs: id: checkRelease run: | CHART_VERSION=$(grep 'version:' ./charts/bpdm/Chart.yaml | head -n1 | awk '{ print $2}') - SNAPSHOT=$(echo $CHART_VERSION | grep 'SNAPSHOT') - if test -z $SNAPSHOT; then IS_RELEASE=true; else IS_RELEASE=false fi + echo "CHART_VERSION: $CHART_VERSION" + SNAPSHOT=$(echo "$CHART_VERSION" | grep 'SNAPSHOT' || echo "" ) + echo "SNAPSHOT: $SNAPSHOT" + if [[ -z "$SNAPSHOT" ]]; then + IS_RELEASE=true + else + IS_RELEASE=false + fi + echo "IS_RELEASE: $IS_RELEASE" echo "isRelease=$IS_RELEASE" >> $GITHUB_OUTPUT - name: Configure Git diff --git a/CHANGELOG.md b/CHANGELOG.md index cc421c683..b4f303769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,29 @@ The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/CHANGELOG.md) of the charts directly. -## [6.1.0] - [tbd] +## [6.1.0] - [2024-07-15] ### Added - BPDM Gate: Post endpoint to upload business partner input data using csv file.(#700) - BPDM Gate: GET endpoint to download the csv file template for business partner upload. (#700) - Apps: Tax Jurisdiction Code to the physical address of a business partner (#955) +- BPDM Orchestrator: Tasks will now be persisted +- BPDM Orchestrator: Tasks now come with a gate record identifier. This makes it possible for cleaning services to match tasks for the same Gate record + +### Changed: + +- BPDM Gate: Fix sending business partner data to the golden record service even when they have no changes +- BPDM Gate: Fix sharing states sometimes taking the wrong task id from the orchestrator + + +## [6.0.2] - [2024-07-03] + +### Changed + +- BPDM Gate: Now sends alternative addresses which are NULL correctly to the Orchestrator +- BPDM Pool: Changed Checksum generation algorithm: Now checksum includes the BPN with prefix + ## [6.0.1] - [2024-05-27] diff --git a/DEPENDENCIES b/DEPENDENCIES index 5b35bcf25..8d69b9061 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -17,10 +17,10 @@ maven/mavencentral/com.github.dasniko/testcontainers-keycloak/3.2.0, Apache-2.0, maven/mavencentral/com.github.docker-java/docker-java-api/3.3.6, Apache-2.0, approved, #10346 maven/mavencentral/com.github.docker-java/docker-java-transport-zerodep/3.3.6, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #15251 maven/mavencentral/com.github.docker-java/docker-java-transport/3.3.6, Apache-2.0, approved, #7942 -maven/mavencentral/com.github.java-json-tools/btf/1.3, Apache-2.0 AND GPL-1.0-or-later AND LGPL-3.0-only AND Apache-2.0 AND LGPL-3.0-only, restricted, #15201 +maven/mavencentral/com.github.java-json-tools/btf/1.3, Apache-2.0 OR LGPL-3.0-only, approved, #15201 maven/mavencentral/com.github.java-json-tools/jackson-coreutils/2.0, Apache-2.0 OR LGPL-3.0-or-later, approved, #15186 maven/mavencentral/com.github.java-json-tools/json-patch/1.13, Apache-2.0 OR LGPL-3.0-or-later, approved, CQ23929 -maven/mavencentral/com.github.java-json-tools/msg-simple/1.2, Apache-2.0 AND LGPL-2.1-or-later AND LGPL-3.0-only AND (Apache-2.0 AND GPL-1.0-or-later AND LGPL-3.0-only) AND Apache-2.0 AND LGPL-3.0-only, restricted, #15239 +maven/mavencentral/com.github.java-json-tools/msg-simple/1.2, Apache-2.0 OR LGPL-3.0-or-later, approved, #15239 maven/mavencentral/com.github.stephenc.jcip/jcip-annotations/1.0-1, Apache-2.0, approved, CQ21949 maven/mavencentral/com.github.tomakehurst/wiremock-jre8-standalone/2.35.2, MIT AND Apache-2.0, approved, #6979 maven/mavencentral/com.google.code.gson/gson/2.10.1, Apache-2.0, approved, #6159 @@ -109,7 +109,7 @@ maven/mavencentral/org.apache.james/apache-mime4j-dom/0.8.9, Apache-2.0, approve maven/mavencentral/org.apache.james/apache-mime4j-storage/0.8.9, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.logging.log4j/log4j-api/2.21.1, Apache-2.0 AND (Apache-2.0 AND LGPL-2.0-or-later), approved, #11079 maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.21.1, Apache-2.0, approved, #15262 -maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.20, Apache-2.0 AND (EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0) AND (CDDL-1.0 OR GPL-2.0-only WITH Classpath-exception-2.0) AND W3C AND CC0-1.0, approved, #5949 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.25, Apache-2.0 AND (EPL-2.0 OR (GPL-2.0 WITH Classpath-exception-2.0)) AND CDDL-1.0 AND (CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)) AND EPL-2.0, approved, #15195 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.20, Apache-2.0, approved, #6997 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.20, Apache-2.0, approved, #7920 maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined @@ -119,12 +119,12 @@ maven/mavencentral/org.awaitility/awaitility/4.2.1, Apache-2.0, approved, #14178 maven/mavencentral/org.checkerframework/checker-qual/3.31.0, MIT, approved, clearlydefined maven/mavencentral/org.eclipse.angus/angus-activation/2.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus maven/mavencentral/org.eclipse.angus/angus-mail/2.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus -maven/mavencentral/org.eclipse.tractusx/bpdm-common-test/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-common/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-gate-api/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator-api/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-pool-api/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-common-test/6.1.0-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-common/6.1.0-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-gate-api/6.1.0-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator-api/6.1.0-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator/6.1.0-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-pool-api/6.1.0-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.flywaydb/flyway-core/9.22.3, Apache-2.0, approved, #15215 maven/mavencentral/org.glassfish.jaxb/codemodel/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.glassfish.jaxb/jaxb-core/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl @@ -173,7 +173,7 @@ maven/mavencentral/org.mockito/mockito-junit-jupiter/5.7.0, MIT, approved, #1142 maven/mavencentral/org.objenesis/objenesis/3.3, Apache-2.0, approved, clearlydefined maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 maven/mavencentral/org.ow2.asm/asm/9.6, BSD-3-Clause, approved, #10776 -maven/mavencentral/org.postgresql/postgresql/42.6.2, BSD-2-Clause AND Apache-2.0, approved, #9159 +maven/mavencentral/org.postgresql/postgresql/42.6.2, BSD-2-Clause AND Apache-2.0, approved, #15238 maven/mavencentral/org.reactivestreams/reactive-streams/1.0.4, CC0-1.0, approved, CQ16332 maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlydefined maven/mavencentral/org.skyscreamer/jsonassert/1.5.1, Apache-2.0, approved, clearlydefined @@ -234,7 +234,7 @@ maven/mavencentral/org.testcontainers/database-commons/1.19.8, Apache-2.0, appro maven/mavencentral/org.testcontainers/jdbc/1.19.8, Apache-2.0, approved, #10348 maven/mavencentral/org.testcontainers/junit-jupiter/1.19.8, MIT, approved, #10344 maven/mavencentral/org.testcontainers/postgresql/1.19.8, MIT, approved, #10350 -maven/mavencentral/org.testcontainers/testcontainers/1.19.8, Apache-2.0 AND MIT, approved, #10347 +maven/mavencentral/org.testcontainers/testcontainers/1.19.8, MIT, approved, #15203 maven/mavencentral/org.webjars/swagger-ui/5.13.0, Apache-2.0, approved, #14547 maven/mavencentral/org.xmlunit/xmlunit-core/2.9.1, Apache-2.0, approved, #6272 maven/mavencentral/org.yaml/snakeyaml/2.2, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #10232 diff --git a/bpdm-cleaning-service-dummy/pom.xml b/bpdm-cleaning-service-dummy/pom.xml index 3e6750c5c..c55d19bdf 100644 --- a/bpdm-cleaning-service-dummy/pom.xml +++ b/bpdm-cleaning-service-dummy/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-cleaning-service-dummy/src/test/kotlin/org/eclipse/tractusx/bpdm/cleaning/service/CleaningServiceApiCallsTest.kt b/bpdm-cleaning-service-dummy/src/test/kotlin/org/eclipse/tractusx/bpdm/cleaning/service/CleaningServiceApiCallsTest.kt index 0a9c904cc..a0547564b 100644 --- a/bpdm-cleaning-service-dummy/src/test/kotlin/org/eclipse/tractusx/bpdm/cleaning/service/CleaningServiceApiCallsTest.kt +++ b/bpdm-cleaning-service-dummy/src/test/kotlin/org/eclipse/tractusx/bpdm/cleaning/service/CleaningServiceApiCallsTest.kt @@ -42,6 +42,7 @@ import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource import java.time.Instant +import java.util.* @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, @@ -373,7 +374,7 @@ class CleaningServiceApiCallsTest @Autowired constructor( // Helper method to create a sample TaskStepReservationResponse private fun createSampleTaskStepReservationResponse(businessPartner: BusinessPartner): TaskStepReservationResponse { - return TaskStepReservationResponse(listOf(TaskStepReservationEntryDto(fixedTaskId, businessPartner)), Instant.MIN) + return TaskStepReservationResponse(listOf(TaskStepReservationEntryDto(fixedTaskId, UUID.randomUUID().toString(), businessPartner)), Instant.MIN) } diff --git a/bpdm-common-test/pom.xml b/bpdm-common-test/pom.xml index 115b3ef4e..8d124fbd3 100644 --- a/bpdm-common-test/pom.xml +++ b/bpdm-common-test/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt index 01fff76c3..39e5a7fb3 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt @@ -32,6 +32,7 @@ class KeyCloakInitializer: ApplicationContextInitializer { companion object { - val postgreSQLContainer = PostgreSQLContainer("postgres:13.2") + val postgreSQLContainer = PostgreSQLContainer("postgres:15.4") .withAccessToHost(true) .withNetwork(Network.SHARED) } diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/util/DbTestHelpers.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/util/DbTestHelpers.kt index 745d27db4..99c1bf8f1 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/util/DbTestHelpers.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/util/DbTestHelpers.kt @@ -37,6 +37,7 @@ class DbTestHelpers(private val entityManagerFactory: EntityManagerFactory?) { truncateDbTablesFromSchema("bpdm") truncateDbTablesFromSchema("bpdmgate") truncateDbTablesFromSchema("bpdm-bridge-dummy") + truncateDbTablesFromSchema("bpdm-orchestrator") } private fun truncateDbTablesFromSchema(dbSchemaName: String) { diff --git a/bpdm-common-test/src/main/resources/keycloak/CX-Central.json b/bpdm-common-test/src/main/resources/keycloak/CX-Central.json index 745d0bb88..1a06d46b2 100644 --- a/bpdm-common-test/src/main/resources/keycloak/CX-Central.json +++ b/bpdm-common-test/src/main/resources/keycloak/CX-Central.json @@ -2626,7 +2626,7 @@ "id.token.claim" : "false", "lightweight.claim" : "false", "access.token.claim" : "true", - "claim.name" : "BPN", + "claim.name" : "bpn", "jsonType.label" : "String" } } ] diff --git a/bpdm-common/pom.xml b/bpdm-common/pom.xml index 33f057b90..835d248f1 100644 --- a/bpdm-common/pom.xml +++ b/bpdm-common/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/util/CollectionUtils.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/util/CollectionUtils.kt index 75e794590..fa930563c 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/util/CollectionUtils.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/util/CollectionUtils.kt @@ -24,6 +24,11 @@ fun MutableCollection.replace(elements: Collection) { addAll(elements) } +fun MutableMap.replace(map: Map) { + clear() + putAll(map) +} + /** * Copy overlapping elements by index from [elements] to [this] collection by applying the [copyFunction]. diff --git a/bpdm-gate-api/pom.xml b/bpdm-gate-api/pom.xml index 479b9fbb8..a54cc2e49 100644 --- a/bpdm-gate-api/pom.xml +++ b/bpdm-gate-api/pom.xml @@ -31,7 +31,7 @@ bpdm-parent org.eclipse.tractusx - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/exception/GateErrorCodes.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/exception/GateErrorCodes.kt index b49ca962f..62af17c1e 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/exception/GateErrorCodes.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/exception/GateErrorCodes.kt @@ -19,6 +19,8 @@ package org.eclipse.tractusx.bpdm.gate.api.exception +import io.swagger.v3.oas.annotations.media.Schema + /** * For every combination of possible errors a separate enum class is defined extending this marker interface. * We need separate enum classes in order to get the correct error codes for each endpoint in the Swagger schema. @@ -30,6 +32,24 @@ enum class BusinessPartnerSharingError : ErrorCode { SharingTimeout, BpnNotInPool, MissingTaskID, + + @Schema(description = "The provided record contains natural person information. ") + NaturalPersonError, + + @Schema(description = "The provided record can not be matched to a legal entity or an address.") + BpnErrorNotFound, + + @Schema(description = "The provided record can not link to a clear legal entity.") + BpnErrorTooManyOptions, + + @Schema(description = "The provided record does not fulfill mandatory validation rules. ") + MandatoryFieldValidationFailed, + + @Schema(description = "The provided record is part of a country that is not allowed to be processed by the GR process (example: Brazil).") + BlacklistCountryPresent, + + @Schema(description = "The provided record contains unalloyed special characters. ") + UnknownSpecialCharacters } enum class ChangeLogOutputError : ErrorCode { diff --git a/bpdm-gate/pom.xml b/bpdm-gate/pom.xml index bf03c4bf8..d735da39c 100644 --- a/bpdm-gate/pom.xml +++ b/bpdm-gate/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SharingStateDb.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SharingStateDb.kt index a8d7a96a3..568166ce2 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SharingStateDb.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SharingStateDb.kt @@ -24,6 +24,7 @@ import org.eclipse.tractusx.bpdm.common.model.BaseEntity import org.eclipse.tractusx.bpdm.gate.api.exception.BusinessPartnerSharingError import org.eclipse.tractusx.bpdm.gate.api.model.SharingStateType import java.time.LocalDateTime +import java.util.* @Entity @@ -36,6 +37,9 @@ class SharingStateDb( @Column(name = "tenant_bpnl", nullable = true) var tenantBpnl: String? = null, + @Column(name = "orchestrator_record_id", nullable = true, unique = true) + var orchestratorRecordId: UUID?, + @Enumerated(EnumType.STRING) @Column(name = "sharing_state_type", nullable = false) var sharingStateType: SharingStateType, diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt index a34377707..749fd7809 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt @@ -42,13 +42,6 @@ object PartnerUploadFileHeader { const val STATES_TYPE_2 = "states2.type" const val ROLES = "roles" const val IS_OWN_COMPANY_DATA = "isOwnCompanyData" - const val LEGAL_ENTITY_BPN = "legalEntity.legalEntityBpn" - const val LEGAL_ENTITY_NAME = "legalEntity.legalName" - const val LEGAL_ENTITY_SHORT_NAME = "legalEntity.shortName" - const val LEGAL_ENTITY_LEGAL_FORM = "legalEntity.legalForm" - const val LEGAL_ENTITY_STATES_VALID_FROM = "legalEntity.states.validFrom" - const val LEGAL_ENTITY_STATES_VALID_TO = "legalEntity.states.validTo" - const val LEGAL_ENTITY_STATES_TYPE = "legalEntity.states.type" const val SITE_BPN = "site.siteBpn" const val SITE_NAME = "site.name" const val SITE_STATES_VALID_FROM = "site.states.validFrom" diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt index d36181e5d..a4ef10d15 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt @@ -91,27 +91,6 @@ data class PartnerUploadFileRow( @CsvBindByName(column = PartnerUploadFileHeader.IS_OWN_COMPANY_DATA) val isOwnCompanyData: String? = null, - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_BPN) - val legalEntityBpn: String? = null, - - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_NAME) - val legalEntityName: String? = null, - - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_SHORT_NAME) - val legalEntityShortName: String? = null, - - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_LEGAL_FORM) - val legalEntityLegalForm: String? = null, - - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_FROM) - val legalEntityStatesValidFrom: String? = null, - - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_TO) - val legalEntityStatesValidTo: String? = null, - - @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_STATES_TYPE) - val legalEntityStatesType: String? = null, - @CsvBindByName(column = PartnerUploadFileHeader.SITE_BPN) val siteBpn: String? = null, diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt index 5bbe159c0..bc4580a14 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt @@ -81,8 +81,11 @@ class BusinessPartnerService( val updatedData = businessPartnerMappings.toBusinessPartnerInput(request, sharingState) val existingInput = existingInputsByExternalId[request.externalId] - sharingStateService.setInitial(sharingState) - upsertFromEntity(existingInput, updatedData).takeIf { it.hadChanges }?.businessPartner + + upsertFromEntity(existingInput, updatedData) + .takeIf { it.hadChanges } + ?.also { sharingStateService.setInitial(sharingState) } + ?.businessPartner } return updatedEntities.map(businessPartnerMappings::toBusinessPartnerInputDto) diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/OrchestratorMappings.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/OrchestratorMappings.kt index 60eaa2387..d30da1851 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/OrchestratorMappings.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/OrchestratorMappings.kt @@ -43,6 +43,13 @@ class OrchestratorMappings( ) { private val logger = KotlinLogging.logger { } + fun toCreateRequest(entity: BusinessPartnerDb): TaskCreateRequestEntry{ + return TaskCreateRequestEntry( + recordId = entity.sharingState.orchestratorRecordId?.toString(), + businessPartner = toOrchestratorDto(entity) + ) + } + fun toOrchestratorDto(entity: BusinessPartnerDb): BusinessPartner { val postalAddress = toPostalAddress(entity) @@ -134,7 +141,7 @@ class OrchestratorMappings( deliveryServiceNumber ) } - } ?: AlternativeAddress.empty, + }, hasChanged = null ) } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt index 84b89b1f0..4e9917c6d 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt @@ -39,10 +39,11 @@ class PartnerUploadService( private val logger = KotlinLogging.logger { } - fun processFile(file: MultipartFile, ownerBpnl: String?): ResponseEntity> { + fun processFile(file: MultipartFile, tenantBpnl: String?): ResponseEntity> { + validateTenantBpnl(tenantBpnl) val csvData: List = PartnerFileUtil.parseCsv(file) - val businessPartnerDtos = PartnerFileUtil.validateAndMapToBusinessPartnerInputRequests(csvData) - val result = businessPartnerService.upsertBusinessPartnersInput(businessPartnerDtos, ownerBpnl) + val businessPartnerDtos = PartnerFileUtil.validateAndMapToBusinessPartnerInputRequests(csvData, tenantBpnl) + val result = businessPartnerService.upsertBusinessPartnersInput(businessPartnerDtos, tenantBpnl) return ResponseEntity.ok(result) } @@ -66,4 +67,10 @@ class PartnerUploadService( return ByteArrayResource(outputStream.toByteArray()) } + private fun validateTenantBpnl(tenantBpnl: String?) { + if (tenantBpnl.isNullOrEmpty() || !tenantBpnl.startsWith("BPNL")) { + throw IllegalArgumentException("tenantBpnl must not be null or empty and must start with 'BPNL'") + } + } + } \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SharingStateService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SharingStateService.kt index 1b34b7cf4..468b0f176 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SharingStateService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SharingStateService.kt @@ -167,6 +167,7 @@ class SharingStateService( SharingStateDb( externalId, sharingStateType = SharingStateType.Ready, + orchestratorRecordId = null, sharingErrorCode = null, sharingErrorMessage = null, sharingProcessStarted = null, diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/TaskCreationService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/TaskCreationService.kt index 0317fdea0..e2fdd25a1 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/TaskCreationService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/TaskCreationService.kt @@ -26,13 +26,14 @@ import org.eclipse.tractusx.bpdm.gate.config.GoldenRecordTaskConfigProperties import org.eclipse.tractusx.bpdm.gate.repository.SharingStateRepository import org.eclipse.tractusx.bpdm.gate.repository.generic.BusinessPartnerRepository import org.eclipse.tractusx.orchestrator.api.client.OrchestrationApiClient -import org.eclipse.tractusx.orchestrator.api.model.BusinessPartner import org.eclipse.tractusx.orchestrator.api.model.TaskClientStateDto import org.eclipse.tractusx.orchestrator.api.model.TaskCreateRequest +import org.eclipse.tractusx.orchestrator.api.model.TaskCreateRequestEntry import org.eclipse.tractusx.orchestrator.api.model.TaskMode import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.util.* @Service class TaskCreationService( @@ -55,17 +56,18 @@ class TaskCreationService( logger.debug { "Found ${foundStates.size} business partners in ready state" } val foundPartners = businessPartnerRepository.findBySharingStateInAndStage(foundStates, StageType.Input) - val orchestratorBusinessPartnersDto = foundPartners.map { orchestratorMappings.toOrchestratorDto(it) } + val orchestratorBusinessPartnersDto = foundPartners.map { orchestratorMappings.toCreateRequest(it) } val createdTasks = createGoldenRecordTasks(TaskMode.UpdateFromSharingMember, orchestratorBusinessPartnersDto) - foundStates.zip(createdTasks).forEach { (state, task) -> - sharingStateService.setPending(state, task.taskId) + foundPartners.zip(createdTasks).forEach { (partner, task) -> + if(partner.sharingState.orchestratorRecordId == null) partner.sharingState.orchestratorRecordId = UUID.fromString(task.recordId) + sharingStateService.setPending(partner.sharingState, task.taskId) } logger.info { "Created ${createdTasks.size} new golden record tasks from ready business partners" } } - private fun createGoldenRecordTasks(mode: TaskMode, orchestratorBusinessPartnersDto: List): List { + private fun createGoldenRecordTasks(mode: TaskMode, orchestratorBusinessPartnersDto: List): List { if (orchestratorBusinessPartnersDto.isEmpty()) return emptyList() diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt index 95a2794c3..afa7ba274 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt @@ -68,7 +68,7 @@ object PartnerFileUtil { * @return A list of BusinessPartnerInputRequest objects derived from the valid CSV rows. * @throws BpdmInvalidPartnerUploadException if any validation errors are encountered during processing. */ - fun validateAndMapToBusinessPartnerInputRequests(csvData: List): List { + fun validateAndMapToBusinessPartnerInputRequests(csvData: List, tenantBpnl: String?): List { val formatter = DateTimeFormatter.ISO_DATE_TIME val validator: Validator = Validation.buildDefaultValidatorFactory().validator val errors = mutableListOf() @@ -89,7 +89,8 @@ object PartnerFileUtil { ), roles = row.roles.toEnumList(BusinessPartnerRole::valueOf, errors, index + 1, PartnerUploadFileHeader.ROLES), isOwnCompanyData = row.isOwnCompanyData?.toBoolean() ?: false, - legalEntity = row.toLegalEntityRepresentationInputDto(formatter, errors, index), + // Legal entity's business partner number is nothing but tenant's partner number who is performing business partner upload action + legalEntity = LegalEntityRepresentationInputDto(legalEntityBpn = tenantBpnl?.takeIf { it.isNotEmpty() }), site = row.toSiteRepresentationInputDto(formatter, errors, index), address = row.toAddressRepresentationInputDto(formatter, errors, index) ) @@ -180,27 +181,6 @@ object PartnerFileUtil { ) } - private fun PartnerUploadFileRow.toLegalEntityRepresentationInputDto( - formatter: DateTimeFormatter, - errors: MutableList, - rowIndex: Int - ): LegalEntityRepresentationInputDto { - return LegalEntityRepresentationInputDto( - legalEntityBpn = legalEntityBpn?.takeIf { it.isNotEmpty() }, - legalName = legalEntityName?.takeIf { it.isNotEmpty() }, - shortName = legalEntityShortName?.takeIf { it.isNotEmpty() }, - legalForm = legalEntityLegalForm?.takeIf { it.isNotEmpty() }, - states = listOfNotNull( - if (!legalEntityStatesValidFrom.isNullOrEmpty() && !legalEntityStatesValidTo.isNullOrEmpty() && !legalEntityStatesType.isNullOrEmpty()) - BusinessPartnerStateDto( - validFrom = parseDate(legalEntityStatesValidFrom, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_FROM), - validTo = parseDate(legalEntityStatesValidTo, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_TO), - type = parseEnum(legalEntityStatesType, BusinessStateType::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.LEGAL_ENTITY_STATES_TYPE) - ) else null - ) - ) - } - private fun PartnerUploadFileRow.toSiteRepresentationInputDto( formatter: DateTimeFormatter, errors: MutableList, diff --git a/bpdm-gate/src/main/resources/db/migration/V6_1_0_4__add_orchestrator_record_id.sql b/bpdm-gate/src/main/resources/db/migration/V6_1_0_4__add_orchestrator_record_id.sql new file mode 100644 index 000000000..7d59cc8fc --- /dev/null +++ b/bpdm-gate/src/main/resources/db/migration/V6_1_0_4__add_orchestrator_record_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE sharing_states +ADD COLUMN orchestrator_record_id UUID unique; \ No newline at end of file diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt index 2d4d0caec..66e3cde88 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt @@ -19,74 +19,49 @@ package org.eclipse.tractusx.bpdm.gate.controller -import com.github.tomakehurst.wiremock.core.WireMockConfiguration -import com.github.tomakehurst.wiremock.junit5.WireMockExtension import org.eclipse.tractusx.bpdm.gate.api.client.GateClient -import org.eclipse.tractusx.bpdm.gate.api.model.SharingStateType import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequest -import org.eclipse.tractusx.bpdm.gate.api.model.response.SharingStateDto -import org.eclipse.tractusx.bpdm.gate.service.TaskCreationService +import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationInputDto +import org.eclipse.tractusx.bpdm.gate.auth.AuthAdminIT import org.eclipse.tractusx.bpdm.gate.util.MockAndAssertUtils +import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer -import org.eclipse.tractusx.bpdm.test.testdata.gate.BusinessPartnerNonVerboseValues import org.eclipse.tractusx.bpdm.test.testdata.gate.BusinessPartnerVerboseValues -import org.eclipse.tractusx.bpdm.test.util.AssertHelpers import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.RegisterExtension import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpStatus import org.springframework.mock.web.MockMultipartFile import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource import org.springframework.web.reactive.function.client.WebClientResponseException import java.nio.file.Files import java.nio.file.Paths @SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, +) +@ActiveProfiles("test") +@ContextConfiguration( + initializers = [ + PostgreSQLContextInitializer::class, + KeyCloakInitializer::class, + AuthAdminIT.SelfClientAsAdminInitializer::class + ] ) -@ActiveProfiles("test-no-auth") -@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) class PartnerUploadControllerIT @Autowired constructor( val testHelpers: DbTestHelpers, - val assertHelpers: AssertHelpers, val gateClient: GateClient, - val taskCreationService: TaskCreationService, val mockAndAssertUtils: MockAndAssertUtils -){ - - companion object { - - @JvmField - @RegisterExtension - val orchestratorWireMockServer: WireMockExtension = WireMockExtension.newInstance().options(WireMockConfiguration.wireMockConfig().dynamicPort()).build() - - @JvmField - @RegisterExtension - val poolWireMockServer: WireMockExtension = WireMockExtension.newInstance().options(WireMockConfiguration.wireMockConfig().dynamicPort()).build() - - - @JvmStatic - @DynamicPropertySource - fun properties(registry: DynamicPropertyRegistry) { - registry.add("bpdm.client.pool.base-url") { poolWireMockServer.baseUrl() } - registry.add("bpdm.client.orchestrator.base-url") { orchestratorWireMockServer.baseUrl() } - } - } +) { @BeforeEach fun beforeEach() { testHelpers.truncateDbTables() - orchestratorWireMockServer.resetAll() - poolWireMockServer.resetAll() - this.mockAndAssertUtils.mockOrchestratorApi(orchestratorWireMockServer) } @Test @@ -104,55 +79,35 @@ class PartnerUploadControllerIT @Autowired constructor( testFileUpload("src/test/resources/testData/invalid_partner_data.csv", HttpStatus.BAD_REQUEST) } - @Test - fun testUploadPartnerDataAndCheckSharingState() { - val bytes = Files.readAllBytes(Paths.get("src/test/resources/testData/valid_partner_data.csv")) - val uploadedFile = MockMultipartFile("valid_partner_data.csv", "valid_partner_data.csv", "text/csv", bytes) - - uploadBusinessPartnerRecordAndShare(uploadedFile) - - val externalId1 = BusinessPartnerVerboseValues.externalId1 - val externalId2 = BusinessPartnerVerboseValues.externalId2 - - val externalIds = listOf(externalId1, externalId2) - - val sharingStatesRequests = listOf( - SharingStateDto( - externalId = externalId1, - sharingStateType = SharingStateType.Pending, - sharingErrorCode = null, - sharingErrorMessage = null, - sharingProcessStarted = null, - taskId = "0" - ), - SharingStateDto( - externalId = externalId2, - sharingStateType = SharingStateType.Pending, - sharingErrorCode = null, - sharingErrorMessage = null, - sharingProcessStarted = null, - taskId = "1" - ) - ) - - val sharingStateResponses = this.mockAndAssertUtils.readSharingStates(externalIds) - assertHelpers.assertRecursively(sharingStateResponses).isEqualTo(sharingStatesRequests) - } - @Test fun testUploadCsvAndValidateBusinessPartnerData() { // Read the bytes of the CSV file val bytes = Files.readAllBytes(Paths.get("src/test/resources/testData/valid_partner_data.csv")) val uploadedFile = MockMultipartFile("valid_partner_data.csv", "valid_partner_data.csv", "text/csv", bytes) + // Only Site and Address expected to be updated from upload partner process. + val expectedSiteAndAddressPartner = BusinessPartnerVerboseValues.bpInputRequestFull.copy( + legalEntity = LegalEntityRepresentationInputDto( + legalEntityBpn = KeyCloakInitializer.TENANT_BPNL, + legalName = null, + shortName = null, + legalForm = null, + states = emptyList() + ) + ) // Upload the CSV file val uploadResponse = gateClient.partnerUpload.uploadPartnerCsvFile(uploadedFile).body!! val expectedResponse = listOf( - BusinessPartnerVerboseValues.bpInputRequestFull, - BusinessPartnerNonVerboseValues.bpInputRequestFull.fastCopy(externalId = BusinessPartnerVerboseValues.externalId2, shortName = "2") + expectedSiteAndAddressPartner, + expectedSiteAndAddressPartner.fastCopy(externalId = BusinessPartnerVerboseValues.externalId2, siteName = "Site Name 2") ) - val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput(listOf(BusinessPartnerVerboseValues.externalId1, BusinessPartnerVerboseValues.externalId2)).content + val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput( + listOf( + BusinessPartnerVerboseValues.externalId1, + BusinessPartnerVerboseValues.externalId2 + ) + ).content this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(uploadResponse, expectedResponse) this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage, expectedResponse) } @@ -175,13 +130,28 @@ class PartnerUploadControllerIT @Autowired constructor( val combinedFile = MockMultipartFile("combined_partner_data.csv", "combined_partner_data.csv", "text/csv", combinedCsv.toByteArray()) val uploadResponse = gateClient.partnerUpload.uploadPartnerCsvFile(combinedFile).body!! + val expectedSiteAndAddressPartner = BusinessPartnerVerboseValues.bpInputRequestFull.copy( + legalEntity = LegalEntityRepresentationInputDto( + legalEntityBpn = KeyCloakInitializer.TENANT_BPNL, + legalName = null, + shortName = null, + legalForm = null, + states = emptyList() + ) + ) + // Perform assertions val expectedResponse = listOf( - BusinessPartnerVerboseValues.bpInputRequestFull, - BusinessPartnerNonVerboseValues.bpInputRequestFull.fastCopy(externalId = BusinessPartnerVerboseValues.externalId2, shortName = "2") + expectedSiteAndAddressPartner, + expectedSiteAndAddressPartner.fastCopy(externalId = BusinessPartnerVerboseValues.externalId2, siteName = "Site Name 2") ) - val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput(listOf(BusinessPartnerVerboseValues.externalId1, BusinessPartnerVerboseValues.externalId2)).content + val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput( + listOf( + BusinessPartnerVerboseValues.externalId1, + BusinessPartnerVerboseValues.externalId2 + ) + ).content this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(uploadResponse, expectedResponse) this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage, expectedResponse) } @@ -210,12 +180,7 @@ class PartnerUploadControllerIT @Autowired constructor( } } - private fun uploadBusinessPartnerRecordAndShare(file: MockMultipartFile) { - gateClient.partnerUpload.uploadPartnerCsvFile(file) - taskCreationService.createTasksForReadyBusinessPartners() - } - - private fun BusinessPartnerInputRequest.fastCopy(externalId: String, shortName: String) = - copy(externalId = externalId, legalEntity = legalEntity.copy(shortName = shortName)) + private fun BusinessPartnerInputRequest.fastCopy(externalId: String, siteName: String) = + copy(externalId = externalId, site = site.copy(name = siteName)) } diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerIT.kt index 4c04d322a..787a45083 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerIT.kt @@ -161,6 +161,7 @@ internal class BusinessPartnerIT @Autowired constructor( return SharingStateDb( externalId = "testExternalId", sharingErrorCode = null, + orchestratorRecordId = null, sharingStateType = SharingStateType.Initial ) } diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/MockAndAssertUtils.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/MockAndAssertUtils.kt index 09b391789..95e377454 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/MockAndAssertUtils.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/MockAndAssertUtils.kt @@ -64,6 +64,7 @@ class MockAndAssertUtils @Autowired constructor( TaskClientStateDto( taskId = "0", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "e3a05ebc-ff59-4d09-bd58-da31d6245701", processingState = TaskProcessingStateDto( resultState = ResultState.Pending, step = TaskStep.CleanAndSync, @@ -77,6 +78,7 @@ class MockAndAssertUtils @Autowired constructor( TaskClientStateDto( taskId = "1", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "f05574ff-4ddd-4360-821a-923203711f85", processingState = TaskProcessingStateDto( resultState = ResultState.Pending, step = TaskStep.CleanAndSync, @@ -90,6 +92,7 @@ class MockAndAssertUtils @Autowired constructor( TaskClientStateDto( taskId = "2", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "8c03850d-d772-4a2e-9845-65211231b38c", processingState = TaskProcessingStateDto( resultState = ResultState.Pending, step = TaskStep.CleanAndSync, @@ -103,6 +106,7 @@ class MockAndAssertUtils @Autowired constructor( TaskClientStateDto( taskId = "3", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "6bebb1aa-935d-467a-afd4-7e8623420a18", processingState = TaskProcessingStateDto( resultState = ResultState.Pending, step = TaskStep.CleanAndSync, @@ -132,6 +136,7 @@ class MockAndAssertUtils @Autowired constructor( TaskClientStateDto( taskId = "0", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "e3a05ebc-ff59-4d09-bd58-da31d6245701", processingState = TaskProcessingStateDto( resultState = ResultState.Success, step = TaskStep.CleanAndSync, @@ -145,6 +150,7 @@ class MockAndAssertUtils @Autowired constructor( TaskClientStateDto( taskId = "1", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "f05574ff-4ddd-4360-821a-923203711f85", processingState = TaskProcessingStateDto( resultState = ResultState.Error, step = TaskStep.CleanAndSync, @@ -172,7 +178,10 @@ class MockAndAssertUtils @Autowired constructor( val taskStateResponse = TaskStateResponse( listOf( TaskClientStateDto( - taskId = "0", businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, processingState = TaskProcessingStateDto( + taskId = "0", + businessPartnerResult = BusinessPartnerGenericCommonValues.businessPartner1, + recordId = "e3a05ebc-ff59-4d09-bd58-da31d6245701", + processingState = TaskProcessingStateDto( resultState = ResultState.Success, step = TaskStep.CleanAndSync, stepState = StepState.Queued, diff --git a/bpdm-gate/src/test/resources/application-test.yml b/bpdm-gate/src/test/resources/application-test.yml index ae02898b0..c9ac8ae2a 100644 --- a/bpdm-gate/src/test/resources/application-test.yml +++ b/bpdm-gate/src/test/resources/application-test.yml @@ -25,6 +25,6 @@ bpdm: fromSharingMember: cron: '-' fromPool: - cron: '_' + cron: '-' check: cron: '-' \ No newline at end of file diff --git a/bpdm-gate/src/test/resources/testData/valid_partner_data.csv b/bpdm-gate/src/test/resources/testData/valid_partner_data.csv index 52f25ee7e..fc58ef249 100644 --- a/bpdm-gate/src/test/resources/testData/valid_partner_data.csv +++ b/bpdm-gate/src/test/resources/testData/valid_partner_data.csv @@ -1,3 +1,3 @@ -externalId,nameParts1,nameParts2,nameParts3,nameParts4,identifiers1.type,identifiers1.value,identifiers1.issuingBody,identifiers2.type,identifiers2.value,identifiers2.issuingBody,identifiers3.type,identifiers3.value,identifiers3.issuingBody,states1.validFrom,states1.validTo,states1.type,states2.validFrom,states2.validTo,states2.type,roles,isOwnCompanyData,legalEntity.legalEntityBpn,legalEntity.legalName,legalEntity.shortName,legalEntity.legalForm,legalEntity.states.validFrom,legalEntity.states.validTo,legalEntity.states.type,site.siteBpn,site.name,site.states.validFrom,site.states.validTo,site.states.type,address.addressBpn,address.name,address.addressType,address.physicalPostalAddress.geographicCoordinates.longitude,address.physicalPostalAddress.geographicCoordinates.latitude,address.physicalPostalAddress.geographicCoordinates.altitude,address.physicalPostalAddress.country,address.physicalPostalAddress.administrativeAreaLevel1,address.physicalPostalAddress.administrativeAreaLevel2,address.physicalPostalAddress.administrativeAreaLevel3,address.physicalPostalAddress.postalCode,address.physicalPostalAddress.city,address.physicalPostalAddress.district,address.physicalPostalAddress.street.namePrefix,address.physicalPostalAddress.street.additionalNamePrefix,address.physicalPostalAddress.street.name,address.physicalPostalAddress.street.nameSuffix,address.physicalPostalAddress.street.additionalNameSuffix,address.physicalPostalAddress.street.houseNumber,address.physicalPostalAddress.street.houseNumberSupplement,address.physicalPostalAddress.street.milestone,address.physicalPostalAddress.street.direction,address.physicalPostalAddress.companyPostalCode,address.physicalPostalAddress.industrialZone,address.physicalPostalAddress.building,address.physicalPostalAddress.floor,address.physicalPostalAddress.door,address.alternativePostalAddress.geographicCoordinates.longitude,address.alternativePostalAddress.geographicCoordinates.latitude,address.alternativePostalAddress.geographicCoordinates.altitude,address.alternativePostalAddress.country,address.alternativePostalAddress.administrativeAreaLevel1,address.alternativePostalAddress.postalCode,address.alternativePostalAddress.city,address.alternativePostalAddress.deliveryServiceType,address.alternativePostalAddress.deliveryServiceQualifier,address.alternativePostalAddress.deliveryServiceNumber,address.states.validForm,address.states.validTo,address.states.type -external-1,Business Partner Name,Company ABC AG,Another Organisation Corp,Catena Test Name,VAT_DE,DE123456789,Agency X,VAT_US,US123456789,Body Y,VAT_FR,FR123456789,,2020-01-01T00:00:00Z,2021-01-01T00:00:00Z,ACTIVE,2019-01-01T00:00:00Z,2022-01-01T00:00:00Z,INACTIVE,SUPPLIER,TRUE,BPNL0000000000XY,Limited Liability Company Name,short1,Limited Liability Company,,,,,Site Name,,,,BPNA0000000001XY,Address Name,LegalAddress,7.619,45.976,4478,US,adminAreaLevel1RegionCode_2, Fulton County,,70547,Atlanta,TODO,,,TODO,,,,B,,direction1,,Industrial Zone Two,Building Two,Floor Two,Door Two,7.619,45.976,4478,DE,adminAreaLevel1RegionCode_2,70547,Stuttgart,PO_BOX,DHL,1234,,, -external-2,Business Partner Name,Company ABC AG,Another Organisation Corp,Catena Test Name,VAT_DE,DE123456789,Agency X,VAT_US,US123456789,Body Y,VAT_FR,FR123456789,,2020-01-01T00:00:00Z,2021-01-01T00:00:00Z,ACTIVE,2019-01-01T00:00:00Z,2022-01-01T00:00:00Z,INACTIVE,SUPPLIER,TRUE,BPNL0000000000XY,Limited Liability Company Name,2,Limited Liability Company,,,,,Site Name,,,,BPNA0000000001XY,Address Name,LegalAddress,7.619,45.976,4478,US,adminAreaLevel1RegionCode_2, Fulton County,,70547,Atlanta,TODO,,,TODO,,,,B,,direction1,,Industrial Zone Two,Building Two,Floor Two,Door Two,7.619,45.976,4478,DE,adminAreaLevel1RegionCode_2,70547,Stuttgart,PO_BOX,DHL,1234,,, \ No newline at end of file +externalId,nameParts1,nameParts2,nameParts3,nameParts4,identifiers1.type,identifiers1.value,identifiers1.issuingBody,identifiers2.type,identifiers2.value,identifiers2.issuingBody,identifiers3.type,identifiers3.value,identifiers3.issuingBody,states1.validFrom,states1.validTo,states1.type,states2.validFrom,states2.validTo,states2.type,roles,isOwnCompanyData,site.siteBpn,site.name,site.states.validFrom,site.states.validTo,site.states.type,address.addressBpn,address.name,address.addressType,address.physicalPostalAddress.geographicCoordinates.longitude,address.physicalPostalAddress.geographicCoordinates.latitude,address.physicalPostalAddress.geographicCoordinates.altitude,address.physicalPostalAddress.country,address.physicalPostalAddress.administrativeAreaLevel1,address.physicalPostalAddress.administrativeAreaLevel2,address.physicalPostalAddress.administrativeAreaLevel3,address.physicalPostalAddress.postalCode,address.physicalPostalAddress.city,address.physicalPostalAddress.district,address.physicalPostalAddress.street.namePrefix,address.physicalPostalAddress.street.additionalNamePrefix,address.physicalPostalAddress.street.name,address.physicalPostalAddress.street.nameSuffix,address.physicalPostalAddress.street.additionalNameSuffix,address.physicalPostalAddress.street.houseNumber,address.physicalPostalAddress.street.houseNumberSupplement,address.physicalPostalAddress.street.milestone,address.physicalPostalAddress.street.direction,address.physicalPostalAddress.companyPostalCode,address.physicalPostalAddress.industrialZone,address.physicalPostalAddress.building,address.physicalPostalAddress.floor,address.physicalPostalAddress.door,address.alternativePostalAddress.geographicCoordinates.longitude,address.alternativePostalAddress.geographicCoordinates.latitude,address.alternativePostalAddress.geographicCoordinates.altitude,address.alternativePostalAddress.country,address.alternativePostalAddress.administrativeAreaLevel1,address.alternativePostalAddress.postalCode,address.alternativePostalAddress.city,address.alternativePostalAddress.deliveryServiceType,address.alternativePostalAddress.deliveryServiceQualifier,address.alternativePostalAddress.deliveryServiceNumber,address.states.validForm,address.states.validTo,address.states.type +external-1,Business Partner Name,Company ABC AG,Another Organisation Corp,Catena Test Name,VAT_DE,DE123456789,Agency X,VAT_US,US123456789,Body Y,VAT_FR,FR123456789,,2020-01-01T00:00:00Z,2021-01-01T00:00:00Z,ACTIVE,2019-01-01T00:00:00Z,2022-01-01T00:00:00Z,INACTIVE,SUPPLIER,TRUE,,Site Name,,,,BPNA0000000001XY,Address Name,LegalAddress,7.619,45.976,4478,US,adminAreaLevel1RegionCode_2, Fulton County,,70547,Atlanta,TODO,,,TODO,,,,B,,direction1,,Industrial Zone Two,Building Two,Floor Two,Door Two,7.619,45.976,4478,DE,adminAreaLevel1RegionCode_2,70547,Stuttgart,PO_BOX,DHL,1234,,, +external-2,Business Partner Name,Company ABC AG,Another Organisation Corp,Catena Test Name,VAT_DE,DE123456789,Agency X,VAT_US,US123456789,Body Y,VAT_FR,FR123456789,,2020-01-01T00:00:00Z,2021-01-01T00:00:00Z,ACTIVE,2019-01-01T00:00:00Z,2022-01-01T00:00:00Z,INACTIVE,SUPPLIER,TRUE,,Site Name 2,,,,BPNA0000000001XY,Address Name,LegalAddress,7.619,45.976,4478,US,adminAreaLevel1RegionCode_2, Fulton County,,70547,Atlanta,TODO,,,TODO,,,,B,,direction1,,Industrial Zone Two,Building Two,Floor Two,Door Two,7.619,45.976,4478,DE,adminAreaLevel1RegionCode_2,70547,Stuttgart,PO_BOX,DHL,1234,,, \ No newline at end of file diff --git a/bpdm-orchestrator-api/pom.xml b/bpdm-orchestrator-api/pom.xml index 8c0c4ea4b..b5c7c35f8 100644 --- a/bpdm-orchestrator-api/pom.xml +++ b/bpdm-orchestrator-api/pom.xml @@ -30,7 +30,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskClientStateDto.kt b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskClientStateDto.kt index 99365a0b6..a33ba5230 100644 --- a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskClientStateDto.kt +++ b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskClientStateDto.kt @@ -28,6 +28,9 @@ data class TaskClientStateDto( @get:Schema(required = true) val taskId: String, + @get:Schema(required = true, description = "The identifier of the gate record for which this task has been created") + val recordId: String, + val businessPartnerResult: BusinessPartner, @get:Schema(required = true) diff --git a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequest.kt b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequest.kt index f4e7280e2..18570cc3c 100644 --- a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequest.kt +++ b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequest.kt @@ -28,6 +28,6 @@ data class TaskCreateRequest( @get:Schema(required = true, description = "The mode affecting which processing steps the business partner goes through") val mode: TaskMode, - @get:ArraySchema(arraySchema = Schema(description = "The list of business partner data to be processed")) - val businessPartners: List + @get:ArraySchema(arraySchema = Schema(description = "The list of tasks to create")) + val requests: List ) diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/model/TaskProcessingState.kt b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequestEntry.kt similarity index 61% rename from bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/model/TaskProcessingState.kt rename to bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequestEntry.kt index 6070d7d3b..79c08f33b 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/model/TaskProcessingState.kt +++ b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskCreateRequestEntry.kt @@ -17,24 +17,13 @@ * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ -package org.eclipse.tractusx.bpdm.orchestrator.model +package org.eclipse.tractusx.orchestrator.api.model -import org.eclipse.tractusx.orchestrator.api.model.* -import java.time.Instant +import io.swagger.v3.oas.annotations.media.Schema -data class TaskProcessingState( - val mode: TaskMode, - var resultState: ResultState, - var errors: List = emptyList(), - - var step: TaskStep, - var stepState: StepState, - - val taskCreatedAt: Instant, - var taskModifiedAt: Instant, - - // only used while in resultState==pending - var taskPendingTimeout: Instant?, - // only used while in final resultState (!=pending) - var taskRetentionTimeout: Instant? +data class TaskCreateRequestEntry( + @get:Schema(description = "The unique identifier for this record which was previously issued by the Orchestrator") + val recordId: String?, + @get:Schema(description = "The business partner data to be processed") + val businessPartner: BusinessPartner ) diff --git a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskErrorType.kt b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskErrorType.kt index 7f18c36a9..8b627e1f4 100644 --- a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskErrorType.kt +++ b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskErrorType.kt @@ -19,7 +19,27 @@ package org.eclipse.tractusx.orchestrator.api.model +import io.swagger.v3.oas.annotations.media.Schema + enum class TaskErrorType { Timeout, - Unspecified + Unspecified, + + @Schema(description = "The provided record contains natural person information. ") + NaturalPersonError, + + @Schema(description = "The provided record can not be matched to a legal entity or an address.") + BpnErrorNotFound, + + @Schema(description = "The provided record can not link to a clear legal entity.") + BpnErrorTooManyOptions, + + @Schema(description = "The provided record does not fulfill mandatory validation rules. ") + MandatoryFieldValidationFailed, + + @Schema(description = "The provided record is part of a country that is not allowed to be processed by the GR process (example: Brazil).") + BlacklistCountryPresent, + + @Schema(description = "The provided record contains unalloyed special characters. ") + UnknownSpecialCharacters } \ No newline at end of file diff --git a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskStepReservationEntryDto.kt b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskStepReservationEntryDto.kt index a67baf2e8..491d4ad5e 100644 --- a/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskStepReservationEntryDto.kt +++ b/bpdm-orchestrator-api/src/main/kotlin/org/eclipse/tractusx/orchestrator/api/model/TaskStepReservationEntryDto.kt @@ -28,6 +28,9 @@ data class TaskStepReservationEntryDto( @get:Schema(description = "The identifier of the reserved task") val taskId: String, + @get:Schema(description = "The identifier of the gate record for which this task has been created") + val recordId: String, + @get:Schema(description = "The business partner data to process") val businessPartner: BusinessPartner ) : RequestWithKey { diff --git a/bpdm-orchestrator/pom.xml b/bpdm-orchestrator/pom.xml index 96e3a0918..023f74c4d 100644 --- a/bpdm-orchestrator/pom.xml +++ b/bpdm-orchestrator/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 @@ -109,6 +109,20 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + + org.flywaydb + flyway-core + diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/Application.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/Application.kt index 078ff3cfa..c58893292 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/Application.kt +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/Application.kt @@ -20,13 +20,12 @@ package org.eclipse.tractusx.bpdm.orchestrator import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.boot.runApplication import org.springframework.scheduling.annotation.EnableScheduling -@SpringBootApplication(exclude = [DataSourceAutoConfiguration::class]) +@SpringBootApplication @ConfigurationPropertiesScan @EnableScheduling class Application diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskController.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskController.kt index 2d5ccef0d..c884b1b68 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskController.kt +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskController.kt @@ -38,8 +38,8 @@ class GoldenRecordTaskController( @PreAuthorize("hasAuthority(${PermissionConfigProperties.CREATE_TASK})") override fun createTasks(createRequest: TaskCreateRequest): TaskCreateResponse { - if (createRequest.businessPartners.size > apiConfigProperties.upsertLimit) - throw BpdmUpsertLimitException(createRequest.businessPartners.size, apiConfigProperties.upsertLimit) + if (createRequest.requests.size > apiConfigProperties.upsertLimit) + throw BpdmUpsertLimitException(createRequest.requests.size, apiConfigProperties.upsertLimit) return goldenRecordTaskService.createTasks(createRequest) } diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/BpnReferenceDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/BpnReferenceDb.kt new file mode 100644 index 000000000..c8a8539f0 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/BpnReferenceDb.kt @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import org.eclipse.tractusx.orchestrator.api.model.BpnReferenceType + +@Embeddable +data class BpnReferenceDb( + @Column(name = "value") + val referenceValue: String?, + @Column(name = "desired_bpn") + val desiredBpn: String?, + @Enumerated(EnumType.STRING) + @Column(name = "type") + val referenceType: BpnReferenceType? +) { + enum class Scope { + LegalEntity, + Site, + LegalAddress, + SiteMainAddress, + AdditionalAddress, + UncategorizedAddress + } +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/BusinessStateDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/BusinessStateDb.kt new file mode 100644 index 000000000..237a053d5 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/BusinessStateDb.kt @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.* +import org.eclipse.tractusx.bpdm.common.model.BusinessStateType + +@Embeddable +data class BusinessStateDb( + @Convert(converter = DbTimestampConverter::class) + @Column(name = "valid_from") + val validFrom: DbTimestamp?, + @Convert(converter = DbTimestampConverter::class) + @Column(name = "valid_to") + val validTo: DbTimestamp?, + @Enumerated(EnumType.STRING) + @Column(name = "type") + val type: BusinessStateType?, + @Enumerated(EnumType.STRING) + @Column(name = "scope", nullable = false) + val scope: Scope +) { + enum class Scope { + LegalEntity, + Site, + Uncategorized, + LegalAddress, + SiteMainAddress, + AdditionalAddress, + UncategorizedAddress + } +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/ConfidenceCriteriaDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/ConfidenceCriteriaDb.kt new file mode 100644 index 000000000..c545b8579 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/ConfidenceCriteriaDb.kt @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.Column +import jakarta.persistence.Convert +import jakarta.persistence.Embeddable + +@Embeddable +data class ConfidenceCriteriaDb( + @Column(name = "shared_by_owner") + val sharedByOwner: Boolean?, + @Column(name = "checked_by_external_datasource") + val checkedByExternalDataSource: Boolean?, + @Column(name = "number_of_sharing_members") + val numberOfSharingMembers: Int?, + @Convert(converter = DbTimestampConverter::class) + @Column(name = "last_confidence_check") + val lastConfidenceCheckAt: DbTimestamp?, + @Convert(converter = DbTimestampConverter::class) + @Column(name = "next_confidence_check") + val nextConfidenceCheckAt: DbTimestamp?, + @Column(name = "confidence_level") + val confidenceLevel: Int? +) { + enum class Scope { + LegalEntity, + Site, + LegalAddress, + SiteMainAddress, + AdditionalAddress, + UncategorizedAddress + } +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/DbTimestamp.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/DbTimestamp.kt new file mode 100644 index 000000000..e0f0eb948 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/DbTimestamp.kt @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import java.time.Instant +import java.time.temporal.ChronoUnit + +/** + * This helper type makes sure that all timestamps on the entities are actually truncated to microseconds (same as in the database) + * + * This makes sure that timestamps in entities and database are always equal even before the entity is persisted in the database + */ +class DbTimestamp(instant: Instant){ + private val truncatedInstant = instant.truncatedTo(ChronoUnit.MICROS) + + val instant get(): Instant = truncatedInstant + + companion object{ + fun now(): DbTimestamp = Instant.now().toTimestamp() + } +} + +fun Instant.toTimestamp(): DbTimestamp = DbTimestamp(this) \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/DbTimestampConverter.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/DbTimestampConverter.kt new file mode 100644 index 000000000..1abcd90e1 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/DbTimestampConverter.kt @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.AttributeConverter +import jakarta.persistence.Converter +import java.sql.Timestamp + +@Converter +class DbTimestampConverter: AttributeConverter { + override fun convertToDatabaseColumn(p0: DbTimestamp?): Timestamp? { + return p0?.let { Timestamp.from(it.instant) } + } + + override fun convertToEntityAttribute(p0: Timestamp?): DbTimestamp? { + return p0?.let { DbTimestamp(p0.toInstant()) } + } +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/GateRecordDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/GateRecordDb.kt new file mode 100644 index 000000000..ad44ee667 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/GateRecordDb.kt @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.* +import org.hibernate.annotations.CreationTimestamp +import org.hibernate.annotations.UpdateTimestamp +import java.time.Instant +import java.util.* + +@Entity +@Table( + name = "gate_records", + indexes = [ + Index(name = "index_gate_records_private_uuid", columnList = "private_uuid") + ] +) +class GateRecordDb ( + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bpdm_sequence") + @SequenceGenerator(name = "bpdm_sequence", sequenceName = "bpdm_sequence", allocationSize = 1) + @Column(name = "id", nullable = false, updatable = false, insertable = false) + val id: Long = 0, + + @Column(updatable = false, nullable = false, name = "CREATED_AT") + @CreationTimestamp + var createdAt: Instant = Instant.now(), + + @Column(nullable = false, name = "UPDATED_AT") + @UpdateTimestamp + var updatedAt: Instant = Instant.now(), + + @Column(name = "public_id", columnDefinition = "UUID", nullable = false, unique = true) + var publicId: UUID, + + @Column(name = "private_id", columnDefinition = "UUID", nullable = false, unique = true) + var privateId: UUID +) \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/GoldenRecordTaskDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/GoldenRecordTaskDb.kt new file mode 100644 index 000000000..a22b25be7 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/GoldenRecordTaskDb.kt @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.* +import org.eclipse.tractusx.bpdm.common.util.replace +import org.eclipse.tractusx.orchestrator.api.model.* +import java.time.Instant +import java.util.* + +@Entity +@Table( + name = "golden_record_tasks", + indexes = [ + Index(name = "index_tasks_uuid", columnList = "uuid"), + Index(name = "index_tasks_step_step_state", columnList = "task_step,task_step_state"), + Index(name = "index_tasks_pending_timeout", columnList = "task_pending_timeout"), + Index(name = "index_tasks_retention_timeout", columnList = "task_retention_timeout") + ] +) +class GoldenRecordTaskDb( + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bpdm_sequence") + @SequenceGenerator(name = "bpdm_sequence", sequenceName = "bpdm_sequence", allocationSize = 1) + @Column(name = "id", nullable = false, updatable = false, insertable = false) + val id: Long = 0, + + @Column(name = "uuid", nullable = false, updatable = false, unique = true, columnDefinition = "uuid") + val uuid: UUID = UUID.randomUUID(), + + @Column(updatable = false, nullable = false, name = "CREATED_AT") + @Convert(converter = DbTimestampConverter::class) + var createdAt: DbTimestamp = DbTimestamp(Instant.now()), + + @Column(nullable = false, name = "UPDATED_AT") + @Convert(converter = DbTimestampConverter::class) + var updatedAt: DbTimestamp = createdAt, + @ManyToOne + @JoinColumn(name = "gate_record_id", nullable = false, foreignKey = ForeignKey(name = "fk_tasks_gate_records")) + var gateRecord: GateRecordDb, + + @Embedded + val processingState: ProcessingState, + @Embedded + val businessPartner: BusinessPartner +) { + fun updateBusinessPartner(newBusinessPartnerData: BusinessPartner){ + with(newBusinessPartnerData){ + //Trick to make sure on a change to the business partner model + // we get a compile error if we don't also adjust this update method + // We discard the created business partner object afterward, it is just there for this check + BusinessPartner( + nameParts = nameParts.also { businessPartner.nameParts.replace(it) }, + identifiers = identifiers.also { businessPartner.identifiers.replace(it) }, + businessStates = businessStates.also { businessPartner.businessStates.replace(it) }, + confidences = confidences.also { businessPartner.confidences.replace(it) }, + addresses = addresses.also { businessPartner.addresses.replace(it) }, + bpnReferences = bpnReferences.also { businessPartner.bpnReferences.replace(it) }, + legalName = legalName.also { businessPartner.legalName = it }, + legalShortName = legalShortName.also { businessPartner.legalShortName = it }, + siteExists = siteExists.also { businessPartner.siteExists = it }, + siteName = siteName.also { businessPartner.siteName = it }, + legalForm = legalForm.also { businessPartner.legalForm = it }, + isCatenaXMemberData = isCatenaXMemberData.also { businessPartner.isCatenaXMemberData = it }, + owningCompany = owningCompany.also { businessPartner.owningCompany = it }, + legalEntityHasChanged = legalEntityHasChanged.also { businessPartner.legalEntityHasChanged = it }, + siteHasChanged = siteHasChanged.also { businessPartner.siteHasChanged = it } + ) + } + } + + @Embeddable + class ProcessingState( + @Enumerated(EnumType.STRING) + @Column(name = "task_mode", nullable = false) + var mode: TaskMode, + @Enumerated(EnumType.STRING) + @Column(name = "task_result_state", nullable = false) + var resultState: ResultState, + @ElementCollection + @CollectionTable(name = "task_errors", joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_errors_tasks"))]) + val errors: MutableList, + @Enumerated(EnumType.STRING) + @Column(name = "task_step", nullable = false) + var step: TaskStep, + @Enumerated(EnumType.STRING) + @Column(name = "task_step_state", nullable = false) + var stepState: StepState, + @Convert(converter = DbTimestampConverter::class) + @Column(name = "task_pending_timeout") + var pendingTimeout: DbTimestamp?, + @Convert(converter = DbTimestampConverter::class) + @Column(name = "task_retention_timeout") + var retentionTimeout: DbTimestamp? + ) + + @Embeddable + class BusinessPartner( + @ElementCollection(fetch = FetchType.EAGER) + @OrderColumn(name = "index", nullable = false) + @CollectionTable( + name = "business_partner_name_parts", + joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_name_parts_tasks"))] + ) + val nameParts: MutableList, + @ElementCollection(fetch = FetchType.EAGER) + @OrderColumn(name = "index", nullable = false) + @CollectionTable( + name = "business_partner_identifiers", + joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_identifiers_tasks"))] + ) + val identifiers: MutableList, + @ElementCollection(fetch = FetchType.EAGER) + @OrderColumn(name = "index", nullable = false) + @CollectionTable( + name = "business_partner_states", + joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_states_tasks"))] + ) + val businessStates: MutableList, + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "scope") + @MapKeyEnumerated(EnumType.STRING) + @CollectionTable( + name = "business_partner_confidences", + joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_confidences_tasks"))], + uniqueConstraints = [UniqueConstraint(name = "uc_business_partner_confidences_task_scope", columnNames = ["task_id", "scope"])] + ) + val confidences: MutableMap, + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "scope") + @MapKeyEnumerated(EnumType.STRING) + @CollectionTable( + name = "business_partner_addresses", + joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_addresses_tasks"))], + uniqueConstraints = [UniqueConstraint(name = "uc_business_partner_addresses_task_scope", columnNames = ["task_id", "scope"])] + ) + val addresses: MutableMap, + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "scope") + @MapKeyEnumerated(EnumType.STRING) + @CollectionTable( + name = "business_partner_bpn_references", + joinColumns = [JoinColumn(name = "task_id", foreignKey = ForeignKey(name = "fk_bpn_references_tasks"))], + uniqueConstraints = [UniqueConstraint(name = "uc_business_partner_bpn_references_task_scope", columnNames = ["task_id", "scope"])] + ) + val bpnReferences: MutableMap, + @Column(name = "legal_name") + var legalName: String?, + @Column(name = "legal_short_name") + var legalShortName: String?, + @Column(name = "site_exists", nullable = false) + var siteExists: Boolean, + @Column(name = "site_name") + var siteName: String?, + @Column(name = "legal_form") + var legalForm: String?, + @Column(name = "is_cx_member") + var isCatenaXMemberData: Boolean?, + @Column(name = "owning_company_bpnl") + var owningCompany: String?, + @Column(name = "legal_entity_has_changed") + var legalEntityHasChanged: Boolean?, + @Column(name = "site_has_changed") + var siteHasChanged: Boolean? + ) +} + + diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/IdentifierDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/IdentifierDb.kt new file mode 100644 index 000000000..08e7f3a1e --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/IdentifierDb.kt @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Embeddable +data class IdentifierDb( + @Column(name = "value") + val value: String?, + @Column(name = "type") + val type: String?, + @Column(name = "issuing_body") + val issuingBody: String?, + @Enumerated(EnumType.STRING) + @Column(name = "scope", nullable = false) + val scope: Scope +) { + enum class Scope { + LegalEntity, + LegalAddress, + Uncategorized, + SiteMainAddress, + AdditionalAddress, + UncategorizedAddress + } +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/NamePartDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/NamePartDb.kt new file mode 100644 index 000000000..e58d3285b --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/NamePartDb.kt @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import org.eclipse.tractusx.orchestrator.api.model.NamePartType + +@Embeddable +data class NamePartDb( + @Column(name = "name", nullable = false) + val name: String, + @Enumerated(EnumType.STRING) + @Column(name = "type") + val type: NamePartType? +) \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/PostalAddressDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/PostalAddressDb.kt new file mode 100644 index 000000000..a9f25f2da --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/PostalAddressDb.kt @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.* +import org.eclipse.tractusx.bpdm.common.model.DeliveryServiceType + +@Embeddable +data class PostalAddressDb( + @Column(name = "address_name") + val addressName: String?, + @Embedded + val physicalAddress: PhysicalAddressDb, + @Embedded + val alternativeAddress: AlternativeAddress, + @Column(name = "has_changed") + val hasChanged: Boolean? +) { + //associate also with equivalent scopes of other entities + enum class Scope( + val bpnReference: BpnReferenceDb.Scope, + val identifier: IdentifierDb.Scope, + val state: BusinessStateDb.Scope, + val confidence: ConfidenceCriteriaDb.Scope + ) { + LegalAddress( + BpnReferenceDb.Scope.LegalAddress, + IdentifierDb.Scope.LegalAddress, + BusinessStateDb.Scope.LegalAddress, + ConfidenceCriteriaDb.Scope.LegalAddress + ), + SiteMainAddress( + BpnReferenceDb.Scope.SiteMainAddress, + IdentifierDb.Scope.SiteMainAddress, + BusinessStateDb.Scope.SiteMainAddress, + ConfidenceCriteriaDb.Scope.SiteMainAddress + ), + AdditionalAddress( + BpnReferenceDb.Scope.AdditionalAddress, + IdentifierDb.Scope.AdditionalAddress, + BusinessStateDb.Scope.AdditionalAddress, + ConfidenceCriteriaDb.Scope.AdditionalAddress + ), + UncategorizedAddress( + BpnReferenceDb.Scope.UncategorizedAddress, + IdentifierDb.Scope.UncategorizedAddress, + BusinessStateDb.Scope.UncategorizedAddress, + ConfidenceCriteriaDb.Scope.UncategorizedAddress + ) + } + + @Embeddable + data class PhysicalAddressDb( + @Embedded + @AttributeOverride(name = "latitude", column = Column(name = "phy_latitude")) + @AttributeOverride(name = "longitude", column = Column(name = "phy_longitude")) + @AttributeOverride(name = "altitude", column = Column(name = "phy_altitude")) + val geographicCoordinates: GeoCoordinate, + @Column(name = "phy_country") + val country: String?, + @Column(name = "phy_admin_area_l1_region") + val administrativeAreaLevel1: String?, + @Column(name = "phy_admin_area_l2") + val administrativeAreaLevel2: String?, + @Column(name = "phy_admin_area_l3") + val administrativeAreaLevel3: String?, + @Column(name = "phy_postcode") + val postalCode: String?, + @Column(name = "phy_city") + val city: String?, + @Column(name = "phy_district_l1") + val district: String?, + @Embedded + val street: Street, + @Column(name = "phy_company_postcode") + val companyPostalCode: String?, + @Column(name = "phy_industrial_zone") + val industrialZone: String?, + @Column(name = "phy_building") + val building: String?, + @Column(name = "phy_floor") + val floor: String?, + @Column(name = "phy_door") + val door: String?, + @Column(name = "phy_tax_jurisdiction") + val taxJurisdictionCode: String? + ) + + @Embeddable + data class AlternativeAddress( + @Column(name = "alt_exists", nullable = false) + val exists: Boolean, + @Embedded + @AttributeOverride(name = "latitude", column = Column(name = "alt_latitude")) + @AttributeOverride(name = "longitude", column = Column(name = "alt_longitude")) + @AttributeOverride(name = "altitude", column = Column(name = "alt_altitude")) + val geographicCoordinates: GeoCoordinate, + @Column(name = "alt_country") + val country: String?, + @Column(name = "alt_admin_area_l1_region") + val administrativeAreaLevel1: String?, + @Column(name = "alt_postcode") + val postalCode: String?, + @Column(name = "alt_city") + val city: String?, + @Column(name = "alt_delivery_service_type") + @Enumerated(EnumType.STRING) + val deliveryServiceType: DeliveryServiceType?, + @Column(name = "alt_delivery_service_qualifier") + val deliveryServiceQualifier: String?, + @Column(name = "alt_delivery_service_number") + val deliveryServiceNumber: String? + ) + + @Embeddable + data class GeoCoordinate( + @Column(name = "longitude") + val longitude: Float?, + @Column(name = "latitude") + val latitude: Float?, + @Column(name = "altitude") + val altitude: Float? + ) + + @Embeddable + data class Street( + @Column(name = "phy_street_name") + val name: String?, + @Column(name = "phy_house_number") + val houseNumber: String?, + @Column(name = "phy_house_number_supplement") + val houseNumberSupplement: String?, + @Column(name = "phy_milestone") + val milestone: String?, + @Column(name = "phy_direction") + val direction: String?, + @Column(name = "phy_street_name_prefix") + val namePrefix: String?, + @Column(name = "phy_street_name_additional_prefix") + val additionalNamePrefix: String?, + @Column(name = "phy_street_name_suffix") + val nameSuffix: String?, + @Column(name = "phy_street_name_additional_suffix") + val additionalNameSuffix: String?, + ) +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/TaskErrorDb.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/TaskErrorDb.kt new file mode 100644 index 000000000..9ca4d476a --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/entity/TaskErrorDb.kt @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.entity + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import org.eclipse.tractusx.orchestrator.api.model.TaskErrorType + +@Embeddable +data class TaskErrorDb( + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + val type: TaskErrorType, + @Column(name = "description", nullable = false) + val description: String +) \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmIllegalStateException.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmIllegalStateException.kt index 06b576d83..2b29cbe07 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmIllegalStateException.kt +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmIllegalStateException.kt @@ -19,13 +19,14 @@ package org.eclipse.tractusx.bpdm.orchestrator.exception -import org.eclipse.tractusx.bpdm.orchestrator.model.TaskProcessingState +import org.eclipse.tractusx.bpdm.orchestrator.entity.GoldenRecordTaskDb import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.ResponseStatus +import java.util.* @ResponseStatus(HttpStatus.BAD_REQUEST) class BpdmIllegalStateException( - taskId: String, - state: TaskProcessingState + taskId: UUID, + state: GoldenRecordTaskDb.ProcessingState ) : RuntimeException("Task with ID '$taskId' is in illegal state for transition: resultState=${state.resultState}, step=${state.step}, stepState=${state.stepState}") \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmRecordNotFoundException.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmRecordNotFoundException.kt new file mode 100644 index 000000000..bd08f1344 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/exception/BpdmRecordNotFoundException.kt @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.exception + +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.ResponseStatus +import java.util.* + +@ResponseStatus(HttpStatus.BAD_REQUEST) +class BpdmRecordNotFoundException ( + recordIds: List +): RuntimeException("The following gate records are not registered: ${recordIds.joinToString()}") \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/model/GoldenRecordTask.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/repository/GateRecordRepository.kt similarity index 72% rename from bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/model/GoldenRecordTask.kt rename to bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/repository/GateRecordRepository.kt index 0bdbd58ca..e88544f5b 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/model/GoldenRecordTask.kt +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/repository/GateRecordRepository.kt @@ -17,12 +17,14 @@ * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ -package org.eclipse.tractusx.bpdm.orchestrator.model -import org.eclipse.tractusx.orchestrator.api.model.BusinessPartner +package org.eclipse.tractusx.bpdm.orchestrator.repository -data class GoldenRecordTask( - val taskId: String, - var businessPartner: BusinessPartner, - val processingState: TaskProcessingState -) +import org.eclipse.tractusx.bpdm.orchestrator.entity.GateRecordDb +import org.springframework.data.repository.CrudRepository +import java.util.* + +interface GateRecordRepository: CrudRepository { + + fun findByPrivateIdIn(privateUuids: Set): Set +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/repository/GoldenRecordTaskRepository.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/repository/GoldenRecordTaskRepository.kt new file mode 100644 index 000000000..df86b6ec5 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/repository/GoldenRecordTaskRepository.kt @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.repository + +import org.eclipse.tractusx.bpdm.orchestrator.entity.DbTimestamp +import org.eclipse.tractusx.bpdm.orchestrator.entity.GoldenRecordTaskDb +import org.eclipse.tractusx.orchestrator.api.model.StepState +import org.eclipse.tractusx.orchestrator.api.model.TaskStep +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.CrudRepository +import org.springframework.data.repository.PagingAndSortingRepository +import org.springframework.stereotype.Repository +import java.util.* + +@Repository +interface GoldenRecordTaskRepository : CrudRepository, PagingAndSortingRepository { + + fun findByUuidIn(uuids: Set): Set + + @Query("SELECT task from GoldenRecordTaskDb task WHERE task.processingState.step = :step AND task.processingState.stepState = :stepState") + fun findByStepAndStepState(step: TaskStep, stepState: StepState, pageable: Pageable): Page + + fun findByProcessingStatePendingTimeoutBefore(time: DbTimestamp): Set + + fun findByProcessingStateRetentionTimeoutBefore(time: DbTimestamp): Set +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskService.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskService.kt index 6f936cb01..f55ee8782 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskService.kt +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskService.kt @@ -21,74 +21,77 @@ package org.eclipse.tractusx.bpdm.orchestrator.service import mu.KotlinLogging import org.eclipse.tractusx.bpdm.orchestrator.config.TaskConfigProperties -import org.eclipse.tractusx.bpdm.orchestrator.exception.BpdmEmptyResultException +import org.eclipse.tractusx.bpdm.orchestrator.entity.GateRecordDb +import org.eclipse.tractusx.bpdm.orchestrator.entity.DbTimestamp +import org.eclipse.tractusx.bpdm.orchestrator.entity.GoldenRecordTaskDb +import org.eclipse.tractusx.bpdm.orchestrator.exception.BpdmRecordNotFoundException import org.eclipse.tractusx.bpdm.orchestrator.exception.BpdmTaskNotFoundException -import org.eclipse.tractusx.bpdm.orchestrator.model.GoldenRecordTask -import org.eclipse.tractusx.bpdm.orchestrator.model.TaskProcessingState +import org.eclipse.tractusx.bpdm.orchestrator.repository.GateRecordRepository +import org.eclipse.tractusx.bpdm.orchestrator.repository.GoldenRecordTaskRepository import org.eclipse.tractusx.orchestrator.api.model.* -import org.eclipse.tractusx.orchestrator.api.model.BusinessPartner +import org.springframework.data.domain.Pageable import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional import java.time.Instant import java.util.* @Service class GoldenRecordTaskService( - private val taskStorage: GoldenRecordTaskStorage, private val goldenRecordTaskStateMachine: GoldenRecordTaskStateMachine, - private val taskConfigProperties: TaskConfigProperties + private val taskConfigProperties: TaskConfigProperties, + private val responseMapper: ResponseMapper, + private val taskRepository: GoldenRecordTaskRepository, + private val gateRecordRepository: GateRecordRepository ) { private val logger = KotlinLogging.logger { } - @Synchronized + @Transactional fun createTasks(createRequest: TaskCreateRequest): TaskCreateResponse { logger.debug { "Creation of new golden record tasks: executing createTasks() with parameters $createRequest" } - return createRequest.businessPartners - .map { businessPartnerGeneric -> taskStorage.addTask(initTask(createRequest, businessPartnerGeneric)) } - .map(::toTaskClientStateDto) + + val gateRecords = getOrCreateGateRecords(createRequest.requests) + + return createRequest.requests.zip(gateRecords) + .map { (request, record) -> goldenRecordTaskStateMachine.initTask(createRequest.mode, request.businessPartner, record) } + .map { task -> responseMapper.toClientState(task, calculateTaskRetentionTimeout(task)) } .let { TaskCreateResponse(createdTasks = it) } } - @Synchronized fun searchTaskStates(stateRequest: TaskStateRequest): TaskStateResponse { logger.debug { "Search for the state of golden record task: executing searchTaskStates() with parameters $stateRequest" } - return stateRequest.taskIds - .mapNotNull { taskId -> taskStorage.getTask(taskId) } // skip missing tasks - .map(::toTaskClientStateDto) + + return stateRequest.taskIds.map { toUUID(it) } + .let { uuids -> taskRepository.findByUuidIn(uuids.toSet()) } + .map { task -> responseMapper.toClientState(task, calculateTaskRetentionTimeout(task)) } .let { TaskStateResponse(tasks = it) } } - @Synchronized + @Transactional fun reserveTasksForStep(reservationRequest: TaskStepReservationRequest): TaskStepReservationResponse { logger.debug { "Reservation of next golden record tasks: executing reserveTasksForStep() with parameters $reservationRequest" } val now = Instant.now() - val tasks = taskStorage.getQueuedTasksByStep(reservationRequest.step, reservationRequest.amount) - tasks.forEach { task -> goldenRecordTaskStateMachine.doReserve(task) } - - val pendingTimeout = tasks.minOfOrNull { calculateTaskPendingTimeout(it.processingState) } ?: now + val foundTasks = taskRepository.findByStepAndStepState(reservationRequest.step, StepState.Queued, Pageable.ofSize(reservationRequest.amount)).content + val reservedTasks = foundTasks.map { task -> goldenRecordTaskStateMachine.doReserve(task) } + val pendingTimeout = reservedTasks.minOfOrNull { calculateTaskPendingTimeout(it) } ?: now - val taskEntries = tasks.map { task -> - TaskStepReservationEntryDto( - taskId = task.taskId, - businessPartner = task.businessPartner - ) - } - - return TaskStepReservationResponse( - reservedTasks = taskEntries, - // property is deprecated - timeout = pendingTimeout - ) + return reservedTasks + .map { task -> TaskStepReservationEntryDto(task.uuid.toString(), task.gateRecord.publicId.toString(), responseMapper.toBusinessPartnerResult(task.businessPartner)) } + .let { reservations -> TaskStepReservationResponse(reservations, pendingTimeout) } } - @Synchronized + @Transactional fun resolveStepResults(resultRequest: TaskStepResultRequest) { logger.debug { "Step results for reserved golden record tasks: executing resolveStepResults() with parameters $resultRequest" } + val uuids = resultRequest.results.map { toUUID(it.taskId) } + val foundTasks = taskRepository.findByUuidIn(uuids.toSet()) + val foundTasksByUuid = foundTasks.associateBy { it.uuid.toString() } + resultRequest.results .forEach { resultEntry -> - val task = taskStorage.getTask(resultEntry.taskId) + val task = foundTasksByUuid[resultEntry.taskId] ?: throw BpdmTaskNotFoundException(resultEntry.taskId) val step = resultRequest.step val errors = resultEntry.errors @@ -96,16 +99,14 @@ class GoldenRecordTaskService( if (errors.isNotEmpty()) { goldenRecordTaskStateMachine.doResolveTaskToError(task, step, errors) - } else if (resultBusinessPartner != null) { - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, step, resultBusinessPartner) } else { - throw BpdmEmptyResultException(resultEntry.taskId) + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, step, resultBusinessPartner) } } } @Scheduled(cron = "\${bpdm.task.timeoutCheckCron}") - @Synchronized + @Transactional fun checkForTimeouts() { try { logger.debug { "Checking for timeouts" } @@ -117,62 +118,56 @@ class GoldenRecordTaskService( } private fun checkForPendingTimeouts() { - taskStorage.getTasksWithPendingTimeoutBefore(Instant.now()) + taskRepository.findByProcessingStatePendingTimeoutBefore(DbTimestamp.now()) .forEach { try { - logger.info { "Setting timeout for task ${it.taskId} after reaching pending timeout" } + logger.info { "Setting timeout for task ${it.uuid} after reaching pending timeout" } goldenRecordTaskStateMachine.doResolveTaskToTimeout(it) } catch (err: RuntimeException) { - logger.error(err) { "Error handling pending timeout for task ${it.taskId}" } + logger.error(err) { "Error handling pending timeout for task ${it.uuid}" } } } } private fun checkForRetentionTimeouts() { - taskStorage.getTasksWithRetentionTimeoutBefore(Instant.now()) + taskRepository.findByProcessingStateRetentionTimeoutBefore(DbTimestamp.now()) .forEach { try { - logger.info { "Removing task ${it.taskId} after reaching retention timeout" } - taskStorage.removeTask(it.taskId) + logger.info { "Removing task ${it.uuid} after reaching retention timeout" } + taskRepository.delete(it) } catch (err: RuntimeException) { - logger.error(err) { "Error handling retention timeout for task ${it.taskId}" } + logger.error(err) { "Error handling retention timeout for task ${it.uuid}" } } } } - private fun initTask( - createRequest: TaskCreateRequest, - businessPartner: BusinessPartner - ) = GoldenRecordTask( - taskId = UUID.randomUUID().toString(), - businessPartner = businessPartner, - processingState = goldenRecordTaskStateMachine.initProcessingState(createRequest.mode) - ) - - private fun toTaskClientStateDto(task: GoldenRecordTask): TaskClientStateDto { - return TaskClientStateDto( - taskId = task.taskId, - processingState = toTaskProcessingStateDto(task.processingState), - businessPartnerResult = task.businessPartner - ) - } + private fun calculateTaskPendingTimeout(task: GoldenRecordTaskDb) = + task.createdAt.instant.plus(taskConfigProperties.taskPendingTimeout) - private fun toTaskProcessingStateDto(processingState: TaskProcessingState): TaskProcessingStateDto { - return TaskProcessingStateDto( - resultState = processingState.resultState, - step = processingState.step, - stepState = processingState.stepState, - errors = processingState.errors, - createdAt = processingState.taskCreatedAt, - modifiedAt = processingState.taskModifiedAt, - // property is deprecated - timeout = calculateTaskRetentionTimeout(processingState) - ) - } + private fun calculateTaskRetentionTimeout(task: GoldenRecordTaskDb) = + task.createdAt.instant.plus(taskConfigProperties.taskRetentionTimeout) + + private fun toUUID(uuidString: String) = + try { + UUID.fromString(uuidString) + } catch (e: IllegalArgumentException) { + throw BpdmTaskNotFoundException(uuidString) + } - private fun calculateTaskPendingTimeout(processingState: TaskProcessingState) = - processingState.taskCreatedAt.plus(taskConfigProperties.taskPendingTimeout) + private fun getOrCreateGateRecords(requests: List): List{ + val privateIds = requests.map { request -> request.recordId?.let { toUUID(it) }} + val notNullPrivateIds = privateIds.filterNotNull() - private fun calculateTaskRetentionTimeout(processingState: TaskProcessingState) = - processingState.taskCreatedAt.plus(taskConfigProperties.taskRetentionTimeout) + val foundRecords = gateRecordRepository.findByPrivateIdIn(notNullPrivateIds.toSet()) + val foundRecordsByPrivateId = foundRecords.associateBy { it.privateId } + val requestedNotFoundRecords = notNullPrivateIds.minus(foundRecordsByPrivateId.keys) + + if(requestedNotFoundRecords.isNotEmpty()) + throw BpdmRecordNotFoundException(requestedNotFoundRecords) + + return privateIds.map { privateId -> + val gateRecord = privateId?.let { foundRecordsByPrivateId[it] } ?: GateRecordDb(publicId = UUID.randomUUID(), privateId = UUID.randomUUID()) + gateRecordRepository.save(gateRecord) + } + } } diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachine.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachine.kt index 3220cbf09..327a6fe09 100644 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachine.kt +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachine.kt @@ -20,120 +20,136 @@ package org.eclipse.tractusx.bpdm.orchestrator.service import mu.KotlinLogging +import org.eclipse.tractusx.bpdm.common.util.replace import org.eclipse.tractusx.bpdm.orchestrator.config.TaskConfigProperties +import org.eclipse.tractusx.bpdm.orchestrator.entity.* import org.eclipse.tractusx.bpdm.orchestrator.exception.BpdmIllegalStateException -import org.eclipse.tractusx.bpdm.orchestrator.model.GoldenRecordTask -import org.eclipse.tractusx.bpdm.orchestrator.model.TaskProcessingState +import org.eclipse.tractusx.bpdm.orchestrator.repository.GoldenRecordTaskRepository import org.eclipse.tractusx.orchestrator.api.model.* -import org.eclipse.tractusx.orchestrator.api.model.BusinessPartner import org.springframework.stereotype.Service import java.time.Instant @Service class GoldenRecordTaskStateMachine( - private val taskConfigProperties: TaskConfigProperties + private val taskConfigProperties: TaskConfigProperties, + private val taskRepository: GoldenRecordTaskRepository, + private val requestMapper: RequestMapper ) { private val logger = KotlinLogging.logger { } - fun initProcessingState(mode: TaskMode): TaskProcessingState { - logger.debug { "Executing initProcessingState() with parameters $mode" } - val now = Instant.now() + fun initTask(mode: TaskMode, initBusinessPartner: BusinessPartner, record: GateRecordDb): GoldenRecordTaskDb { + logger.debug { "Executing initProcessingState() with parameters mode: $mode and business partner data: $initBusinessPartner" } val initialStep = getInitialStep(mode) - - return TaskProcessingState( + val initProcessingState = GoldenRecordTaskDb.ProcessingState( mode = mode, resultState = ResultState.Pending, - step = initialStep, + errors = mutableListOf(), stepState = StepState.Queued, + pendingTimeout = Instant.now().plus(taskConfigProperties.taskPendingTimeout).toTimestamp(), + retentionTimeout = null + ) - taskCreatedAt = now, - taskModifiedAt = now, - - taskPendingTimeout = now.plus(taskConfigProperties.taskPendingTimeout), - taskRetentionTimeout = null + val initialTask = GoldenRecordTaskDb( + gateRecord = record, + processingState = initProcessingState, + businessPartner = requestMapper.toBusinessPartner(initBusinessPartner) ) + + return taskRepository.save(initialTask) } - fun doReserve(task: GoldenRecordTask) { + fun doReserve(task: GoldenRecordTaskDb): GoldenRecordTaskDb { logger.debug { "Executing doReserve() with parameters $task" } val state = task.processingState - val now = Instant.now() if (state.resultState != ResultState.Pending || state.stepState != StepState.Queued) { - throw BpdmIllegalStateException(task.taskId, state) + throw BpdmIllegalStateException(task.uuid, state) } // reserved for current step - state.stepState = StepState.Reserved - state.taskModifiedAt = now + task.processingState.stepState = StepState.Reserved + task.updatedAt = DbTimestamp(Instant.now()) + + return taskRepository.save(task) } - fun doResolveTaskToSuccess(task: GoldenRecordTask, step: TaskStep, resultBusinessPartner: BusinessPartner) { + fun resolveTaskStepToSuccess( + task: GoldenRecordTaskDb, + step: TaskStep, + resultBusinessPartner: BusinessPartner + ): GoldenRecordTaskDb { logger.debug { "Executing doResolveTaskToSuccess() with parameters $task // $step and $resultBusinessPartner" } val state = task.processingState - val now = Instant.now() if (state.resultState != ResultState.Pending || state.stepState != StepState.Reserved || state.step != step) { - throw BpdmIllegalStateException(task.taskId, state) + throw BpdmIllegalStateException(task.uuid, state) } val nextStep = getNextStep(state.mode, state.step) if (nextStep != null) { // still steps left to process -> queued for next step - state.step = nextStep - state.stepState = StepState.Queued - state.taskModifiedAt = now + task.processingState.toStep(nextStep) } else { // last step finished -> set resultState and stepState to success - resolveStateToSuccess(state) + task.processingState.toSuccess() } - task.businessPartner = resultBusinessPartner + task.updateBusinessPartner(requestMapper.toBusinessPartner(resultBusinessPartner)) + task.updatedAt = DbTimestamp(Instant.now()) + + return taskRepository.save(task) } - fun doResolveTaskToError(task: GoldenRecordTask, step: TaskStep, errors: List) { + fun doResolveTaskToError(task: GoldenRecordTaskDb, step: TaskStep, errors: List): GoldenRecordTaskDb { logger.debug { "Executing doResolveTaskToError() with parameters $task // $step and $errors" } val state = task.processingState if (state.resultState != ResultState.Pending || state.stepState != StepState.Reserved || state.step != step) { - throw BpdmIllegalStateException(task.taskId, state) + throw BpdmIllegalStateException(task.uuid, state) } + task.processingState.toError(errors.map { requestMapper.toTaskError(it) }) + task.updatedAt = DbTimestamp(Instant.now()) - resolveStateToError(state, errors) + return taskRepository.save(task) } - fun doResolveTaskToTimeout(task: GoldenRecordTask) { + fun doResolveTaskToTimeout(task: GoldenRecordTaskDb): GoldenRecordTaskDb { val state = task.processingState if (state.resultState != ResultState.Pending) { - throw BpdmIllegalStateException(task.taskId, state) + throw BpdmIllegalStateException(task.uuid, state) } - val errors = listOf(TaskErrorDto(TaskErrorType.Timeout, "Timeout reached")) - resolveStateToError(state, errors) + val errors = listOf(TaskErrorDb(TaskErrorType.Timeout, "Timeout reached")) + task.processingState.toError(errors) + task.updatedAt = DbTimestamp(Instant.now()) + + return taskRepository.save(task) } - private fun resolveStateToSuccess(state: TaskProcessingState) { - val now = Instant.now() - state.resultState = ResultState.Success - state.stepState = StepState.Success - state.taskModifiedAt = now - state.taskPendingTimeout = null - state.taskRetentionTimeout = now.plus(taskConfigProperties.taskRetentionTimeout) + private fun GoldenRecordTaskDb.ProcessingState.toStep(nextStep: TaskStep) { + step = nextStep + stepState = StepState.Queued + } + + private fun GoldenRecordTaskDb.ProcessingState.toSuccess() { + resultState = ResultState.Success + stepState = StepState.Success + pendingTimeout = null + retentionTimeout = Instant.now().plus(taskConfigProperties.taskRetentionTimeout).toTimestamp() + } - private fun resolveStateToError(state: TaskProcessingState, errors: List) { - val now = Instant.now() - state.resultState = ResultState.Error - state.errors = errors - state.stepState = StepState.Error - state.taskModifiedAt = now - state.taskPendingTimeout = null - state.taskRetentionTimeout = now.plus(taskConfigProperties.taskRetentionTimeout) + private fun GoldenRecordTaskDb.ProcessingState.toError(newErrors: List) { + resultState = ResultState.Error + stepState = StepState.Error + errors.replace(newErrors) + pendingTimeout = null + retentionTimeout = Instant.now().plus(taskConfigProperties.taskRetentionTimeout).toTimestamp() } private fun getInitialStep(mode: TaskMode): TaskStep { diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStorage.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStorage.kt deleted file mode 100644 index 30e7ec955..000000000 --- a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStorage.kt +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available 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. - * - * SPDX-License-Identifier: Apache-2.0 - ******************************************************************************/ - -package org.eclipse.tractusx.bpdm.orchestrator.service - -import org.eclipse.tractusx.bpdm.orchestrator.exception.BpdmDuplicateTaskIdException -import org.eclipse.tractusx.bpdm.orchestrator.model.GoldenRecordTask -import org.eclipse.tractusx.orchestrator.api.model.ResultState -import org.eclipse.tractusx.orchestrator.api.model.StepState -import org.eclipse.tractusx.orchestrator.api.model.TaskStep -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class GoldenRecordTaskStorage { - - private val tasks: MutableList = mutableListOf() - - // Needed for testing - fun clear() { - tasks.clear() - } - - fun addTask(task: GoldenRecordTask): GoldenRecordTask { - return task.also { - if (getTask(it.taskId) != null) { - throw BpdmDuplicateTaskIdException(it.taskId) - } - tasks.add(it) - } - } - - fun removeTask(taskId: String) { - tasks.removeIf { - it.taskId == taskId - } - } - - fun getTask(taskId: String) = - tasks.firstOrNull { it.taskId == taskId } - - fun getQueuedTasksByStep(step: TaskStep, amount: Int): List = - tasks - .filter { - val state = it.processingState - state.resultState == ResultState.Pending && - state.stepState == StepState.Queued && - state.step == step - } - .take(amount) - - fun getTasksWithPendingTimeoutBefore(timestamp: Instant): List = - tasks - .filter { - val state = it.processingState - state.taskPendingTimeout?.isBefore(timestamp) ?: false - } - - fun getTasksWithRetentionTimeoutBefore(timestamp: Instant): List = - tasks - .filter { - val state = it.processingState - state.taskRetentionTimeout?.isBefore(timestamp) ?: false - } -} diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/RequestMapper.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/RequestMapper.kt new file mode 100644 index 000000000..210296b59 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/RequestMapper.kt @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.service + +import org.eclipse.tractusx.bpdm.orchestrator.entity.* +import org.eclipse.tractusx.orchestrator.api.model.* +import org.springframework.stereotype.Service + +@Service +class RequestMapper { + + fun toBusinessPartner(businessPartner: BusinessPartner) = + with(businessPartner){ + GoldenRecordTaskDb.BusinessPartner( + nameParts = toNameParts(businessPartner), + identifiers = toIdentifiers(businessPartner), + businessStates = toStates(businessPartner), + confidences = toConfidences(businessPartner), + addresses = toPostalAddresses(businessPartner), + bpnReferences = toBpnReferences(businessPartner), + legalName = legalEntity.legalName, + legalShortName = legalEntity.legalShortName, + siteExists = site != null, + siteName = site?.siteName, + legalForm = legalEntity.legalForm, + isCatenaXMemberData = legalEntity.isCatenaXMemberData, + owningCompany = owningCompany, + legalEntityHasChanged = legalEntity.hasChanged, + siteHasChanged = site?.hasChanged + ) + } + + fun toTaskError(error: TaskErrorDto) = + with(error) { + TaskErrorDb(type = type, description = description) + } + + fun toNameParts(businessPartner: BusinessPartner) = + mutableListOf( + businessPartner.uncategorized.nameParts.map { NamePartDb(it, null) }, + businessPartner.nameParts.map { NamePartDb(it.name, it.type) } + ).flatten().toMutableList() + + + fun toIdentifier(identifier: Identifier, scope: IdentifierDb.Scope) = + with(identifier) { + IdentifierDb(value, type, issuingBody, scope) + } + + + fun toIdentifiers(businessPartner: BusinessPartner) = + IdentifierDb.Scope.entries.mapNotNull { scope -> + when (scope) { + IdentifierDb.Scope.LegalEntity -> businessPartner.legalEntity.identifiers + IdentifierDb.Scope.LegalAddress -> businessPartner.legalEntity.legalAddress.identifiers + IdentifierDb.Scope.SiteMainAddress -> businessPartner.site?.siteMainAddress?.identifiers + IdentifierDb.Scope.AdditionalAddress -> businessPartner.additionalAddress?.identifiers + IdentifierDb.Scope.Uncategorized -> businessPartner.uncategorized.identifiers + IdentifierDb.Scope.UncategorizedAddress -> businessPartner.uncategorized.address?.identifiers + }?.map { toIdentifier(it, scope) } + }.flatten().toMutableList() + + fun toState(state: BusinessState, scope: BusinessStateDb.Scope) = + with(state) { + BusinessStateDb(validFrom?.toTimestamp(), validTo?.toTimestamp(), type, scope) + } + + fun toStates(businessPartner: BusinessPartner) = + BusinessStateDb.Scope.entries.mapNotNull { scope -> + when (scope) { + BusinessStateDb.Scope.LegalEntity -> businessPartner.legalEntity.states + BusinessStateDb.Scope.Site -> businessPartner.site?.states + BusinessStateDb.Scope.LegalAddress -> businessPartner.legalEntity.legalAddress.states + BusinessStateDb.Scope.SiteMainAddress -> businessPartner.site?.siteMainAddress?.states + BusinessStateDb.Scope.AdditionalAddress -> businessPartner.additionalAddress?.states + BusinessStateDb.Scope.Uncategorized -> businessPartner.uncategorized.states + BusinessStateDb.Scope.UncategorizedAddress -> businessPartner.uncategorized.address?.states + }?.map { toState(it, scope) } + }.flatten().toMutableList() + + fun toConfidence(confidenceCriteria: ConfidenceCriteria) = + with(confidenceCriteria) { + ConfidenceCriteriaDb( + sharedByOwner, + checkedByExternalDataSource, + numberOfSharingMembers, + lastConfidenceCheckAt?.toTimestamp(), + nextConfidenceCheckAt?.toTimestamp(), + confidenceLevel + ) + } + + fun toConfidences(businessPartner: BusinessPartner) = + ConfidenceCriteriaDb.Scope.entries.mapNotNull { scope -> + when (scope) { + ConfidenceCriteriaDb.Scope.LegalEntity -> businessPartner.legalEntity.confidenceCriteria + ConfidenceCriteriaDb.Scope.Site -> businessPartner.site?.confidenceCriteria + ConfidenceCriteriaDb.Scope.LegalAddress -> businessPartner.legalEntity.legalAddress.confidenceCriteria + ConfidenceCriteriaDb.Scope.SiteMainAddress -> businessPartner.site?.siteMainAddress?.confidenceCriteria + ConfidenceCriteriaDb.Scope.AdditionalAddress -> businessPartner.additionalAddress?.confidenceCriteria + ConfidenceCriteriaDb.Scope.UncategorizedAddress -> businessPartner.uncategorized.address?.confidenceCriteria + }?.let { scope to toConfidence(it) } + }.toMap().toMutableMap() + + fun toPostalAddress(postalAddress: PostalAddress, scope: PostalAddressDb.Scope) = + with(postalAddress) { + PostalAddressDb( + addressName = addressName, + physicalAddress = toPhysicalAddress(physicalAddress), + alternativeAddress = toAlternativeAddress(alternativeAddress), + hasChanged = hasChanged + ) + } + + fun toPostalAddresses(businessPartner: BusinessPartner) = + PostalAddressDb.Scope.entries.mapNotNull { scope -> + when (scope) { + PostalAddressDb.Scope.LegalAddress -> businessPartner.legalEntity.legalAddress + PostalAddressDb.Scope.SiteMainAddress -> businessPartner.site?.siteMainAddress + PostalAddressDb.Scope.AdditionalAddress -> businessPartner.additionalAddress + PostalAddressDb.Scope.UncategorizedAddress -> businessPartner.uncategorized.address + }?.let { scope to toPostalAddress(it, scope) } + }.toMap().toMutableMap() + + + fun toBpnReference(bpnReference: BpnReference) = + with(bpnReference) { + BpnReferenceDb( + referenceValue = referenceValue, + desiredBpn = desiredBpn, + referenceType = referenceType + ) + } + + fun toBpnReferences(businessPartner: BusinessPartner) = + BpnReferenceDb.Scope.entries.mapNotNull { scope -> + when (scope) { + BpnReferenceDb.Scope.LegalEntity -> businessPartner.legalEntity.bpnReference + BpnReferenceDb.Scope.Site -> businessPartner.site?.bpnReference + BpnReferenceDb.Scope.LegalAddress -> businessPartner.legalEntity.legalAddress.bpnReference + BpnReferenceDb.Scope.SiteMainAddress -> businessPartner.site?.siteMainAddress?.bpnReference + BpnReferenceDb.Scope.AdditionalAddress -> businessPartner.additionalAddress?.bpnReference + BpnReferenceDb.Scope.UncategorizedAddress -> businessPartner.uncategorized.address?.bpnReference + }?.let { scope to toBpnReference(it) } + }.toMap().toMutableMap() + + fun toPhysicalAddress(physicalAddress: PhysicalAddress) = + with(physicalAddress) { + PostalAddressDb.PhysicalAddressDb( + geographicCoordinates = toGeoCoordinate(geographicCoordinates), + country = country, + administrativeAreaLevel1 = administrativeAreaLevel1, + administrativeAreaLevel2 = administrativeAreaLevel2, + administrativeAreaLevel3 = administrativeAreaLevel3, + postalCode = postalCode, + city = city, + district = district, + street = toStreet(street), + companyPostalCode = companyPostalCode, + industrialZone = industrialZone, + building = building, + floor = floor, + door = door, + taxJurisdictionCode = taxJurisdictionCode + ) + } + + fun toAlternativeAddress(alternativeAddress: AlternativeAddress?) = + alternativeAddress?.let { + with(alternativeAddress) { + PostalAddressDb.AlternativeAddress( + exists = true, + geographicCoordinates = toGeoCoordinate(geographicCoordinates), + country = country, + administrativeAreaLevel1 = administrativeAreaLevel1, + postalCode = postalCode, + city = city, + deliveryServiceType = deliveryServiceType, + deliveryServiceQualifier = deliveryServiceQualifier, + deliveryServiceNumber = deliveryServiceNumber + ) + } + } ?: PostalAddressDb.AlternativeAddress( + exists = false, + geographicCoordinates = PostalAddressDb.GeoCoordinate( + longitude = null, + latitude = null, + altitude = null + ), + country = null, + administrativeAreaLevel1 = null, + postalCode = null, + city = null, + deliveryServiceType = null, + deliveryServiceQualifier = null, + deliveryServiceNumber = null + ) + + + fun toGeoCoordinate(geoCoordinate: GeoCoordinate) = + with(geoCoordinate) { + PostalAddressDb.GeoCoordinate(longitude, latitude, altitude) + } + + fun toStreet(street: Street) = + with(street) { + PostalAddressDb.Street( + name, + houseNumber, + houseNumberSupplement, + milestone, + direction, + namePrefix, + additionalNamePrefix, + nameSuffix, + additionalNameSuffix + ) + } + +} \ No newline at end of file diff --git a/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/ResponseMapper.kt b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/ResponseMapper.kt new file mode 100644 index 000000000..192751046 --- /dev/null +++ b/bpdm-orchestrator/src/main/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/ResponseMapper.kt @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.orchestrator.service + +import org.eclipse.tractusx.bpdm.orchestrator.entity.* +import org.eclipse.tractusx.orchestrator.api.model.* +import org.springframework.stereotype.Service +import java.time.Instant + +@Service +class ResponseMapper { + + + fun toClientState(task: GoldenRecordTaskDb, timeout: Instant) = + with(task) { + TaskClientStateDto( + taskId = task.uuid.toString(), + recordId = task.gateRecord.privateId.toString(), + businessPartnerResult = toBusinessPartnerResult(businessPartner), + processingState = toProcessingState(task, timeout) + ) + } + + fun toProcessingState(task: GoldenRecordTaskDb, timeout: Instant) = + with(task.processingState) { + TaskProcessingStateDto( + resultState = resultState, + step = step, + stepState = stepState, + errors = errors.map { toTaskError(it) }, + createdAt = task.createdAt.instant, + modifiedAt = task.updatedAt.instant, + timeout = timeout + ) + } + + fun toTaskError(taskError: TaskErrorDb) = + with(taskError) { + TaskErrorDto(type = type, description = description) + } + + + fun toBusinessPartnerResult(businessPartner: GoldenRecordTaskDb.BusinessPartner) = + with(businessPartner) { + BusinessPartner( + nameParts = toCategorizedNameParts(nameParts), + owningCompany = owningCompany, + uncategorized = toUncategorizedProperties(businessPartner), + legalEntity = toLegalEntity(businessPartner), + site = toSite(businessPartner), + additionalAddress = toPostalAddress(businessPartner, PostalAddressDb.Scope.AdditionalAddress) + ) + } + + + fun toUncategorizedProperties(businessPartner: GoldenRecordTaskDb.BusinessPartner) = + UncategorizedProperties( + nameParts = toUncategorizedNameParts(businessPartner.nameParts), + identifiers = toIdentifiers(businessPartner, IdentifierDb.Scope.Uncategorized), + states = toStates(businessPartner, BusinessStateDb.Scope.Uncategorized), + address = toPostalAddress(businessPartner, PostalAddressDb.Scope.UncategorizedAddress) + ) + + fun toLegalEntity(businessPartner: GoldenRecordTaskDb.BusinessPartner) = + with(businessPartner) { + LegalEntity( + bpnReference = toBpnReference(businessPartner, BpnReferenceDb.Scope.LegalEntity), + legalName = legalName, + legalShortName = legalShortName, + legalForm = legalForm, + identifiers = toIdentifiers(businessPartner, IdentifierDb.Scope.LegalEntity), + states = toStates(businessPartner, BusinessStateDb.Scope.LegalEntity), + confidenceCriteria = toConfidence(businessPartner, ConfidenceCriteriaDb.Scope.LegalEntity), + isCatenaXMemberData = isCatenaXMemberData, + hasChanged = legalEntityHasChanged, + legalAddress = toPostalAddressOrEmpty(businessPartner, PostalAddressDb.Scope.LegalAddress)!! + ) + } + + fun toSite(businessPartner: GoldenRecordTaskDb.BusinessPartner) = + businessPartner.takeIf { it.siteExists }?.let { + with(businessPartner) { + Site( + bpnReference = toBpnReference(businessPartner, BpnReferenceDb.Scope.Site), + siteName = siteName, + states = toStates(businessPartner, BusinessStateDb.Scope.Site), + confidenceCriteria = toConfidence(businessPartner, ConfidenceCriteriaDb.Scope.Site), + hasChanged = siteHasChanged, + siteMainAddress = toPostalAddress(businessPartner, PostalAddressDb.Scope.SiteMainAddress) + ) + } + } + + + fun toCategorizedNameParts(nameParts: List) = + nameParts.filter { it.type != null }.map { NamePart(it.name, it.type!!) } + + fun toUncategorizedNameParts(nameParts: List) = + nameParts.filter { it.type == null }.map { it.name } + + fun toIdentifiers(businessPartner: GoldenRecordTaskDb.BusinessPartner, scope: IdentifierDb.Scope) = + businessPartner.identifiers.filter { it.scope == scope }.map { Identifier(it.value, it.type, it.issuingBody) } + + fun toStates(businessPartner: GoldenRecordTaskDb.BusinessPartner, scope: BusinessStateDb.Scope) = + businessPartner.businessStates.filter { it.scope == scope }.map { BusinessState(it.validFrom?.instant, it.validTo?.instant, it.type) } + + fun toConfidence(businessPartner: GoldenRecordTaskDb.BusinessPartner, scope: ConfidenceCriteriaDb.Scope) = + businessPartner.confidences[scope]?.let { toConfidence(it) } ?: ConfidenceCriteria.empty + + fun toConfidence(confidenceCriteria: ConfidenceCriteriaDb) = + with(confidenceCriteria) { + ConfidenceCriteria( + sharedByOwner = sharedByOwner, + checkedByExternalDataSource = checkedByExternalDataSource, + numberOfSharingMembers = numberOfSharingMembers, + lastConfidenceCheckAt = lastConfidenceCheckAt?.instant, + nextConfidenceCheckAt = nextConfidenceCheckAt?.instant, + confidenceLevel = confidenceLevel + ) + } + + fun toBpnReference(bpnReference: BpnReferenceDb) = + with(bpnReference) { + BpnReference(referenceValue = referenceValue, desiredBpn = desiredBpn, referenceType = referenceType) + } + + fun toBpnReference(businessPartner: GoldenRecordTaskDb.BusinessPartner, scope: BpnReferenceDb.Scope) = + businessPartner.bpnReferences[scope]?.let { toBpnReference(it) } ?: BpnReference.empty + + fun toPostalAddressOrEmpty(businessPartner: GoldenRecordTaskDb.BusinessPartner, scope: PostalAddressDb.Scope) = + toPostalAddress(businessPartner, scope) + + fun toPostalAddress(businessPartner: GoldenRecordTaskDb.BusinessPartner, scope: PostalAddressDb.Scope) = + businessPartner.addresses[scope]?.let { postalAddress -> + with(postalAddress) { + PostalAddress( + bpnReference = toBpnReference(businessPartner, scope.bpnReference), + addressName = addressName, + identifiers = toIdentifiers(businessPartner, scope.identifier), + states = toStates(businessPartner, scope.state), + confidenceCriteria = toConfidence(businessPartner, scope.confidence), + physicalAddress = toPhysicalAddress(physicalAddress), + alternativeAddress = toAlternativeAddress(alternativeAddress), + hasChanged = hasChanged + ) + } + } + + fun toPhysicalAddress(physicalAddress: PostalAddressDb.PhysicalAddressDb) = + with(physicalAddress) { + PhysicalAddress( + geographicCoordinates = toGeoCoordinate(geographicCoordinates), + country = country, + administrativeAreaLevel1 = administrativeAreaLevel1, + administrativeAreaLevel2 = administrativeAreaLevel2, + administrativeAreaLevel3 = administrativeAreaLevel3, + postalCode = postalCode, + city = city, + district = district, + street = toStreet(street), + companyPostalCode = companyPostalCode, + industrialZone = industrialZone, + building = building, + floor = floor, + door = door, + taxJurisdictionCode = taxJurisdictionCode + ) + } + + fun toAlternativeAddress(alternativeAddress: PostalAddressDb.AlternativeAddress) = + alternativeAddress.takeIf { it.exists }?.let { + with(alternativeAddress) { + AlternativeAddress( + geographicCoordinates = toGeoCoordinate(geographicCoordinates), + country = country, + administrativeAreaLevel1 = administrativeAreaLevel1, + postalCode = postalCode, + city = city, + deliveryServiceType = deliveryServiceType, + deliveryServiceQualifier = deliveryServiceQualifier, + deliveryServiceNumber = deliveryServiceNumber + ) + } + } + + + fun toGeoCoordinate(geoCoordinate: PostalAddressDb.GeoCoordinate) = + with(geoCoordinate) { + GeoCoordinate(longitude = longitude, latitude = latitude, altitude = altitude) + } + + fun toStreet(street: PostalAddressDb.Street) = + with(street) { + Street( + name, houseNumber, + houseNumberSupplement = houseNumberSupplement, + milestone = milestone, + direction = direction, + namePrefix = namePrefix, + additionalNamePrefix = additionalNamePrefix, + nameSuffix = nameSuffix, + additionalNameSuffix = additionalNameSuffix + ) + } + +} diff --git a/bpdm-orchestrator/src/main/resources/application.yml b/bpdm-orchestrator/src/main/resources/application.yml index ff9806afe..c13106848 100644 --- a/bpdm-orchestrator/src/main/resources/application.yml +++ b/bpdm-orchestrator/src/main/resources/application.yml @@ -74,7 +74,11 @@ bpdm: cleanAndSync: create_result_cleanAndSync # Name of permission to post results for tasks in step 'PoolSync' poolSync: create_result_poolSync - + datasource: + # Host name of the used datasource + host: localhost + # The database schema to use for this application + schema: bpdm-orchestrator server: # Change default port to avoid clash with other BPDM applications port: 8085 @@ -86,6 +90,28 @@ spring: jwt: issuer-uri: ${bpdm.security.auth-server-url}/realms/${bpdm.security.realm} jwk-set-uri: ${bpdm.security.auth-server-url}/realms/${bpdm.security.realm}/protocol/openid-connect/certs + datasource: + # We use postgres as datasource + driverClassName: org.postgresql.Driver + # No password on default (Change for production) + password: '' + # Connect to postgres database over jdbc protocol + url: jdbc:postgresql://${bpdm.datasource.host}:5432/bpdm + # Default username for BPDM applications + username: bpdm + flyway: + # Activate flyway for automatic database migration + enabled: true + # Apply migrations to this application's default schema + schemas: ${bpdm.datasource.schema} + jpa: + properties: + # We use hibernate as JPA implementation + hibernate: + # Hibernate should assume the default schema of this application on default + default_schema: ${bpdm.datasource.schema} + create_empty_composites: + enabled: true logging: pattern: # Use BPDM custom log pattern diff --git a/bpdm-orchestrator/src/main/resources/db/migration/V6_1_0_0__create_tables.sql b/bpdm-orchestrator/src/main/resources/db/migration/V6_1_0_0__create_tables.sql new file mode 100644 index 000000000..8e7ff49df --- /dev/null +++ b/bpdm-orchestrator/src/main/resources/db/migration/V6_1_0_0__create_tables.sql @@ -0,0 +1,240 @@ +create sequence bpdm_sequence start with 1 increment by 1; + +create table business_partner_addresses ( + alt_exists boolean not null, + alt_altitude float4, + alt_latitude float4, + alt_longitude float4, + has_changed boolean, + phy_altitude float4, + phy_latitude float4, + phy_longitude float4, + task_id bigint not null, + address_name varchar(255), + alt_admin_area_l1_region varchar(255), + alt_city varchar(255), + alt_country varchar(255), + alt_delivery_service_number varchar(255), + alt_delivery_service_qualifier varchar(255), + alt_delivery_service_type varchar(255) check ( + alt_delivery_service_type in ('PO_BOX', 'PRIVATE_BAG', 'BOITE_POSTALE') + ), + alt_postcode varchar(255), + phy_admin_area_l1_region varchar(255), + phy_admin_area_l2 varchar(255), + phy_admin_area_l3 varchar(255), + phy_building varchar(255), + phy_city varchar(255), + phy_company_postcode varchar(255), + phy_country varchar(255), + phy_direction varchar(255), + phy_district_l1 varchar(255), + phy_door varchar(255), + phy_floor varchar(255), + phy_house_number varchar(255), + phy_house_number_supplement varchar(255), + phy_industrial_zone varchar(255), + phy_milestone varchar(255), + phy_postcode varchar(255), + phy_street_name varchar(255), + phy_street_name_additional_prefix varchar(255), + phy_street_name_additional_suffix varchar(255), + phy_street_name_prefix varchar(255), + phy_street_name_suffix varchar(255), + phy_tax_jurisdiction varchar(255), + scope varchar(255) not null check ( + scope in ( + 'LegalAddress', + 'SiteMainAddress', + 'AdditionalAddress', + 'UncategorizedAddress' + ) + ), + constraint uc_business_partner_addresses_task_scope unique (task_id, scope) +); + +create table business_partner_bpn_references ( + task_id bigint not null, + desired_bpn varchar(255), + scope varchar(255) not null check ( + scope in ( + 'LegalEntity', + 'Site', + 'LegalAddress', + 'SiteMainAddress', + 'AdditionalAddress', + 'UncategorizedAddress' + ) + ), + type varchar(255) check (type in ('Bpn', 'BpnRequestIdentifier')), + value varchar(255), + constraint uc_business_partner_bpn_references_task_scope unique (task_id, scope) +); + +create table business_partner_confidences ( + checked_by_external_datasource boolean, + confidence_level integer, + number_of_sharing_members integer, + shared_by_owner boolean, + last_confidence_check TIMESTAMP, + next_confidence_check TIMESTAMP, + task_id bigint not null, + scope varchar(255) not null check ( + scope in ( + 'LegalEntity', + 'Site', + 'Uncategorized', + 'LegalAddress', + 'SiteMainAddress', + 'AdditionalAddress', + 'UncategorizedAddress' + ) + ), + constraint uc_business_partner_confidences_task_scope unique (task_id, scope) +); + +create table business_partner_identifiers ( + task_id bigint not null, + index integer not null, + issuing_body varchar(255), + scope varchar(255) not null check ( + scope in ( + 'LegalEntity', + 'Uncategorized', + 'LegalAddress', + 'SiteMainAddress', + 'AdditionalAddress', + 'UncategorizedAddress' + ) + ), + type varchar(255), + value varchar(255) +); + +create table business_partner_name_parts ( + index integer not null, + task_id bigint not null, + name varchar(255) not null, + type varchar(255) check ( + type in ( + 'LegalName', + 'ShortName', + 'LegalForm', + 'SiteName', + 'AddressName' + ) + ) +); + +create table business_partner_states ( + task_id bigint not null, + index integer not null, + valid_from TIMESTAMP, + valid_to TIMESTAMP, + scope varchar(255) not null check ( + scope in ( + 'LegalEntity', + 'Site', + 'Uncategorized', + 'LegalAddress', + 'SiteMainAddress', + 'AdditionalAddress', + 'UncategorizedAddress' + ) + ), + type varchar(255) check (type in ('ACTIVE', 'INACTIVE')) +); + +create table golden_record_tasks ( + gate_record_id bigint not null, + is_cx_member boolean, + legal_entity_has_changed boolean, + site_exists boolean not null, + site_has_changed boolean, + created_at TIMESTAMP not null, + id bigint not null, + task_pending_timeout TIMESTAMP, + task_retention_timeout TIMESTAMP, + updated_at TIMESTAMP not null, + uuid uuid not null unique, + legal_form varchar(255), + legal_name varchar(255), + legal_short_name varchar(255), + owning_company_bpnl varchar(255), + site_name varchar(255), + task_mode varchar(255) not null check ( + task_mode in ('UpdateFromSharingMember', 'UpdateFromPool') + ), + task_result_state varchar(255) not null check ( + task_result_state in ('Pending', 'Success', 'Error') + ), + task_step varchar(255) not null check (task_step in ('CleanAndSync', 'PoolSync', 'Clean')), + task_step_state varchar(255) not null check ( + task_step_state in ('Queued', 'Reserved', 'Success', 'Error') + ), + primary key (id) +); + +create table task_errors ( + task_id bigint not null, + description varchar(255) not null, + type varchar(255) not null check (type in ('Timeout', 'Unspecified', 'NaturalPersonError', 'BpnErrorNotFound', 'BpnErrorTooManyOptions','MandatoryFieldValidationFailed', 'BlacklistCountryPresent', 'UnknownSpecialCharacters')) +); + +create table gate_records ( + id bigint not null, + created_at TIMESTAMP not null, + updated_at TIMESTAMP not null, + private_id uuid not null unique, + public_id uuid not null unique, + primary key(id) +); + + +create index index_tasks_uuid on golden_record_tasks (uuid); + +create index index_tasks_step_step_state on golden_record_tasks (task_step, task_step_state); + +create index index_tasks_pending_timeout on golden_record_tasks (task_pending_timeout); + +create index index_tasks_retention_timeout on golden_record_tasks (task_retention_timeout); + +alter table + if exists golden_record_tasks +add + constraint fk_tasks_gate_records foreign key (gate_record_id) references gate_records; + +alter table + if exists business_partner_addresses +add + constraint fk_addresses_tasks foreign key (task_id) references golden_record_tasks; + +alter table + if exists business_partner_bpn_references +add + constraint fk_bpn_references_tasks foreign key (task_id) references golden_record_tasks; + +alter table + if exists business_partner_confidences +add + constraint fk_confidences_tasks foreign key (task_id) references golden_record_tasks; + +alter table + if exists business_partner_identifiers +add + constraint fk_identifiers_tasks foreign key (task_id) references golden_record_tasks; + +alter table + if exists business_partner_name_parts +add + constraint fk_name_parts_tasks foreign key (task_id) references golden_record_tasks; + +alter table + if exists business_partner_states +add + constraint fk_states_tasks foreign key (task_id) references golden_record_tasks; + +alter table + if exists task_errors +add + constraint fk_errors_tasks foreign key (task_id) references golden_record_tasks; diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/ApplicationTests.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/ApplicationTests.kt index ceaa0aa50..bbacb2d89 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/ApplicationTests.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/ApplicationTests.kt @@ -19,12 +19,15 @@ package org.eclipse.tractusx.bpdm.orchestrator +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") +@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) class ApplicationTests { @Test diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthAdminIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthAdminIT.kt index dfe28de02..dc45015ad 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthAdminIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthAdminIT.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.orchestrator.auth import org.eclipse.tractusx.bpdm.orchestrator.Application import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.containers.SelfClientInitializer import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType @@ -31,6 +32,7 @@ import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class]) @ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, KeyCloakInitializer::class, AuthAdminIT.SelfClientAsAdminInitializer::class ]) diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskCreatorIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskCreatorIT.kt index 5ea99fe16..6b0af6c73 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskCreatorIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskCreatorIT.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.orchestrator.auth import org.eclipse.tractusx.bpdm.orchestrator.Application import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.containers.SelfClientInitializer import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType @@ -31,6 +32,7 @@ import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class]) @ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, KeyCloakInitializer::class, AuthTaskCreatorIT.SelfClientAsTaskCreatorInitializer::class ]) diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanAndSyncIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanAndSyncIT.kt index 1bb2cae08..c4c771eb9 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanAndSyncIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanAndSyncIT.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.orchestrator.auth import org.eclipse.tractusx.bpdm.orchestrator.Application import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.containers.SelfClientInitializer import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType @@ -31,6 +32,7 @@ import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class]) @ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, KeyCloakInitializer::class, AuthTaskProcessorCleanAndSyncIT.SelfClientAsTaskProcessorCleanAndSyncInitializer::class ]) diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanIT.kt index df8b595bb..244f201ee 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorCleanIT.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.orchestrator.auth import org.eclipse.tractusx.bpdm.orchestrator.Application import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.containers.SelfClientInitializer import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType @@ -31,6 +32,7 @@ import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class]) @ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, KeyCloakInitializer::class, AuthTaskProcessorCleanIT.SelfClientAsTaskProcessorCleanInitializer::class ]) diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorPoolSyncIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorPoolSyncIT.kt index eceac91e2..d6953f037 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorPoolSyncIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/AuthTaskProcessorPoolSyncIT.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.orchestrator.auth import org.eclipse.tractusx.bpdm.orchestrator.Application import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.containers.SelfClientInitializer import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType @@ -31,6 +32,7 @@ import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class]) @ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, KeyCloakInitializer::class, AuthTaskProcessorPoolSyncIT.SelfClientAsTaskProcessorPoolSyncInitializer::class ]) diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/NoAuthIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/NoAuthIT.kt index 278b7b0ee..b8937f31b 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/NoAuthIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/auth/NoAuthIT.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.orchestrator.auth import org.eclipse.tractusx.bpdm.orchestrator.Application import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType import org.eclipse.tractusx.orchestrator.api.client.OrchestrationApiClient @@ -30,6 +31,7 @@ import org.springframework.test.context.ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class]) @ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, KeyCloakInitializer::class ]) class NoAuthIT @Autowired constructor( diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskControllerIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskControllerIT.kt index 5f59bdbb9..fbbcba011 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskControllerIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/controller/GoldenRecordTaskControllerIT.kt @@ -23,19 +23,21 @@ import org.assertj.core.api.Assertions.* import org.assertj.core.api.ThrowableAssert import org.assertj.core.data.TemporalUnitOffset import org.eclipse.tractusx.bpdm.orchestrator.config.TaskConfigProperties -import org.eclipse.tractusx.bpdm.orchestrator.service.GoldenRecordTaskStorage +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.BusinessPartnerTestDataFactory +import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers import org.eclipse.tractusx.orchestrator.api.client.OrchestrationApiClient import org.eclipse.tractusx.orchestrator.api.model.* -import org.eclipse.tractusx.orchestrator.api.model.BusinessPartner import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpStatus +import org.springframework.test.context.ContextConfiguration import org.springframework.web.reactive.function.client.WebClientResponseException import java.time.Instant import java.time.temporal.ChronoUnit +import java.util.* val WITHIN_ALLOWED_TIME_OFFSET: TemporalUnitOffset = within(1, ChronoUnit.SECONDS) @@ -49,10 +51,11 @@ val WITHIN_ALLOWED_TIME_OFFSET: TemporalUnitOffset = within(1, ChronoUnit.SECOND "bpdm.task.taskRetentionTimeout=5s" ] ) +@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) class GoldenRecordTaskControllerIT @Autowired constructor( - val orchestratorClient: OrchestrationApiClient, - val taskConfigProperties: TaskConfigProperties, - val goldenRecordTaskStorage: GoldenRecordTaskStorage + private val orchestratorClient: OrchestrationApiClient, + private val taskConfigProperties: TaskConfigProperties, + private val dbTestHelpers: DbTestHelpers ) { private val testDataFactory = BusinessPartnerTestDataFactory() @@ -61,7 +64,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @BeforeEach fun cleanUp() { - goldenRecordTaskStorage.clear() + dbTestHelpers.truncateDbTables() } /** @@ -74,7 +77,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `request cleaning task`() { // create tasks and check response - val createdTasks = createTasks().createdTasks + val createdTasks = createTasksWithoutRecordId().createdTasks assertThat(createdTasks.size).isEqualTo(2) @@ -84,7 +87,6 @@ class GoldenRecordTaskControllerIT @Autowired constructor( val processingState = stateDto.processingState assertProcessingStateDto(processingState, ResultState.Pending, TaskStep.CleanAndSync, StepState.Queued) assertThat(processingState.errors).isEqualTo(emptyList()) - assertThat(processingState.createdAt).isEqualTo(processingState.modifiedAt) } // check if response is consistent with searchTaskStates response @@ -100,7 +102,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `request cleaning task in alternative mode`() { // create tasks and check response - val createdTasks = createTasks(TaskMode.UpdateFromPool).createdTasks + val createdTasks = createTasksWithoutRecordId(TaskMode.UpdateFromPool).createdTasks assertThat(createdTasks.size).isEqualTo(2) val processingState = createdTasks[0].processingState @@ -121,7 +123,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `request reservation`() { // create tasks - val createdTasks = createTasks(TaskMode.UpdateFromSharingMember).createdTasks + val createdTasks = createTasksWithoutRecordId(TaskMode.UpdateFromSharingMember).createdTasks assertThat(createdTasks.size).isEqualTo(2) // reserve tasks @@ -161,7 +163,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `request reservation for wrong step`() { // create tasks - createTasks(TaskMode.UpdateFromPool) + createTasksWithoutRecordId(TaskMode.UpdateFromPool) // try reservation for wrong step val reservedTasks = reserveTasks(TaskStep.CleanAndSync).reservedTasks @@ -182,7 +184,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `post cleaning results for all steps`() { // create tasks - createTasks() + createTasksWithoutRecordId() // reserve task for step==CleanAndSync val reservedTasks1 = reserveTasks(TaskStep.CleanAndSync, 1).reservedTasks @@ -251,7 +253,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `post cleaning result with error`() { // create tasks - createTasks() + createTasksWithoutRecordId() // reserve task for step==CleanAndSync val taskId = reserveTasks(TaskStep.CleanAndSync, 1).reservedTasks.single().taskId @@ -291,7 +293,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( ) assertBadRequestException { - createTasks(businessPartners = businessPartners) + createTasksWithoutRecordId(businessPartners = businessPartners) } } @@ -344,7 +346,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `expect exceptions on posting inconsistent task results`() { // create tasks - createTasks() + createTasksWithoutRecordId() // reserve tasks val tasksIds = reserveTasks(TaskStep.CleanAndSync).reservedTasks.map { it.taskId } @@ -418,7 +420,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `wait for task pending and retention timeout`() { // create tasks - val createdTasks = createTasks().createdTasks + val createdTasks = createTasksWithoutRecordId().createdTasks val taskIds = createdTasks.map { it.taskId } // check for state Pending @@ -454,7 +456,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `wait for task retention timeout after success`() { // create single task in UpdateFromPool mode (only one step) - createTasks(TaskMode.UpdateFromPool, listOf(defaultBusinessPartner1)) + createTasksWithoutRecordId(TaskMode.UpdateFromPool, listOf(defaultBusinessPartner1)) // reserve task val reservedTask = reserveTasks(TaskStep.Clean).reservedTasks.single() @@ -487,7 +489,7 @@ class GoldenRecordTaskControllerIT @Autowired constructor( @Test fun `wait for task retention timeout after error`() { // create single task in UpdateFromPool mode (only one step) - createTasks(TaskMode.UpdateFromPool, listOf(defaultBusinessPartner1)) + createTasksWithoutRecordId(TaskMode.UpdateFromPool, listOf(defaultBusinessPartner1)) // reserve task val reservedTask = reserveTasks(TaskStep.Clean).reservedTasks.single() @@ -518,13 +520,42 @@ class GoldenRecordTaskControllerIT @Autowired constructor( assertThat(foundTasks.size).isZero() } - private fun createTasks(mode: TaskMode = TaskMode.UpdateFromSharingMember, businessPartners: List? = null): TaskCreateResponse = - orchestratorClient.goldenRecordTasks.createTasks( - TaskCreateRequest( - mode = mode, - businessPartners = businessPartners ?: listOf(defaultBusinessPartner1, defaultBusinessPartner2) - ) - ) + @Test + fun `create task for existing gate record`(){ + //Create records by creating tasks first + val existingRecordIds = createTasksWithoutRecordId().createdTasks.map { it.recordId } + + val requestsWithRecords = listOf(defaultBusinessPartner1, defaultBusinessPartner2) + .zip(existingRecordIds) + .map { (bp, recordId) -> TaskCreateRequestEntry(recordId, bp) } + + requestsWithRecords.forEach { assertThat(it.recordId).isNotNull() } + + val tasksWithRecords = orchestratorClient.goldenRecordTasks.createTasks(TaskCreateRequest(TaskMode.UpdateFromSharingMember, requestsWithRecords)).createdTasks + + tasksWithRecords.zip(existingRecordIds).forEach { (actualTask, expectedRecordId) -> assertThat(actualTask.recordId).isEqualTo(expectedRecordId) } + } + + @Test + fun `expect exception on creating task for non-existing gate record`(){ + val unknownRecordId = UUID.randomUUID() + + val requestWithUnknownRecord = TaskCreateRequestEntry(unknownRecordId.toString(), defaultBusinessPartner1) + + assertBadRequestException{ + createTasks(entries = listOf(requestWithUnknownRecord)) + } + } + + private fun createTasks(mode: TaskMode = TaskMode.UpdateFromSharingMember, + entries: List? = null + ): TaskCreateResponse{ + val resolvedEntries = entries ?: listOf(defaultBusinessPartner1, defaultBusinessPartner2).map { bp -> TaskCreateRequestEntry(null, bp) } + return orchestratorClient.goldenRecordTasks.createTasks(TaskCreateRequest(mode = mode, requests = resolvedEntries)) + } + + private fun createTasksWithoutRecordId(mode: TaskMode = TaskMode.UpdateFromSharingMember, businessPartners: List? = null): TaskCreateResponse = + createTasks(mode, (businessPartners ?: listOf(defaultBusinessPartner1, defaultBusinessPartner2)).map{ bp -> TaskCreateRequestEntry(null, bp) }) private fun reserveTasks(step: TaskStep, amount: Int = 3) = orchestratorClient.goldenRecordTasks.reserveTasksForStep( diff --git a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachineIT.kt b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachineIT.kt index ec004b36e..cf1c685c3 100644 --- a/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachineIT.kt +++ b/bpdm-orchestrator/src/test/kotlin/org/eclipse/tractusx/bpdm/orchestrator/service/GoldenRecordTaskStateMachineIT.kt @@ -24,52 +24,73 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.data.TemporalUnitOffset import org.eclipse.tractusx.bpdm.orchestrator.config.TaskConfigProperties +import org.eclipse.tractusx.bpdm.orchestrator.entity.GateRecordDb +import org.eclipse.tractusx.bpdm.orchestrator.entity.GoldenRecordTaskDb import org.eclipse.tractusx.bpdm.orchestrator.exception.BpdmIllegalStateException -import org.eclipse.tractusx.bpdm.orchestrator.model.GoldenRecordTask -import org.eclipse.tractusx.bpdm.orchestrator.model.TaskProcessingState +import org.eclipse.tractusx.bpdm.orchestrator.repository.GateRecordRepository +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.BusinessPartnerTestDataFactory +import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers import org.eclipse.tractusx.orchestrator.api.model.* +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.transaction.annotation.Transactional import java.time.Instant import java.time.temporal.ChronoUnit +import java.util.* val WITHIN_ALLOWED_TIME_OFFSET: TemporalUnitOffset = Assertions.within(10, ChronoUnit.SECONDS) -val TASK_ID = "TASK-ID" @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = [ + "bpdm.security.enabled=false", "bpdm.task.taskPendingTimeout=3s", "bpdm.task.taskRetentionTimeout=5s" ] ) +@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) class GoldenRecordTaskStateMachineIT @Autowired constructor( - val goldenRecordTaskStateMachine: GoldenRecordTaskStateMachine, - val taskConfigProperties: TaskConfigProperties + private val goldenRecordTaskStateMachine: GoldenRecordTaskStateMachine, + private val taskConfigProperties: TaskConfigProperties, + private val gateRecordRepository: GateRecordRepository, + private val dbTestHelpers: DbTestHelpers ) { private val testDataFactory = BusinessPartnerTestDataFactory() private val businessPartnerFull = testDataFactory.createFullBusinessPartner("full") + private lateinit var gateRecord: GateRecordDb + + @BeforeEach + fun cleanUp() { + dbTestHelpers.truncateDbTables() + gateRecord = gateRecordRepository.save(GateRecordDb(publicId = UUID.randomUUID(), privateId = UUID.randomUUID())) + } + + /** * WHEN creating an initial TaskProcessingState * THEN expect the correct content */ @Test + @Transactional fun `initial state`() { val now = Instant.now() - val state = goldenRecordTaskStateMachine.initProcessingState(TaskMode.UpdateFromSharingMember) + val task = goldenRecordTaskStateMachine.initTask(TaskMode.UpdateFromSharingMember, businessPartnerFull, gateRecord) + val state = task.processingState - assertProcessingStateDto(state, ResultState.Pending, TaskStep.CleanAndSync, StepState.Queued) + assertProcessingState(state, ResultState.Pending, TaskStep.CleanAndSync, StepState.Queued) assertThat(state.mode).isEqualTo(TaskMode.UpdateFromSharingMember) assertThat(state.errors.size).isEqualTo(0) - assertThat(state.taskCreatedAt).isCloseTo(now, WITHIN_ALLOWED_TIME_OFFSET) - assertThat(state.taskModifiedAt).isEqualTo(state.taskCreatedAt) - assertThat(state.taskPendingTimeout).isCloseTo(now.plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) - assertThat(state.taskRetentionTimeout).isNull() + assertThat(task.createdAt.instant).isCloseTo(now, WITHIN_ALLOWED_TIME_OFFSET) + assertThat(task.updatedAt.instant).isCloseTo(now, WITHIN_ALLOWED_TIME_OFFSET) + assertThat(state.pendingTimeout?.instant).isCloseTo(now.plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) + assertThat(state.retentionTimeout).isNull() } /** @@ -80,17 +101,18 @@ class GoldenRecordTaskStateMachineIT @Autowired constructor( * THEN expect an error */ @Test + @Transactional fun `walk through all UpdateFromSharingMember steps`() { // new task - val task = initTask(TaskMode.UpdateFromSharingMember) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.CleanAndSync, StepState.Queued) + val task = goldenRecordTaskStateMachine.initTask(TaskMode.UpdateFromSharingMember, businessPartnerFull, gateRecord) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.CleanAndSync, StepState.Queued) // taskPendingTimeout has been set - val taskPendingTimeout = task.processingState.taskPendingTimeout - assertThat(task.processingState.taskPendingTimeout).isCloseTo(Instant.now().plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) + val taskPendingTimeout = task.processingState.pendingTimeout + assertThat(task.processingState.pendingTimeout?.instant).isCloseTo(Instant.now().plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) // 1st reserve goldenRecordTaskStateMachine.doReserve(task) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.CleanAndSync, StepState.Reserved) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.CleanAndSync, StepState.Reserved) // Can't reserve again! assertThatThrownBy { @@ -98,40 +120,44 @@ class GoldenRecordTaskStateMachineIT @Autowired constructor( }.isInstanceOf(BpdmIllegalStateException::class.java) // 1st resolve - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, TaskStep.CleanAndSync, businessPartnerFull) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.PoolSync, StepState.Queued) + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, TaskStep.CleanAndSync, businessPartnerFull) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.PoolSync, StepState.Queued) // Can't resolve again! assertThatThrownBy { - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, TaskStep.CleanAndSync, businessPartnerFull) + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, TaskStep.CleanAndSync, businessPartnerFull) }.isInstanceOf(BpdmIllegalStateException::class.java) // 2nd reserve goldenRecordTaskStateMachine.doReserve(task) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.PoolSync, StepState.Reserved) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.PoolSync, StepState.Reserved) // Can't resolve with wrong step (CleanAndSync)! assertThatThrownBy { - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, TaskStep.CleanAndSync, businessPartnerFull) + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, TaskStep.CleanAndSync, businessPartnerFull) }.isInstanceOf(BpdmIllegalStateException::class.java) // taskPendingTimeout is still the same - assertThat(task.processingState.taskPendingTimeout).isEqualTo(taskPendingTimeout) + assertThat(task.processingState.pendingTimeout).isEqualTo(taskPendingTimeout) // 2nd and final resolve - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, TaskStep.PoolSync, businessPartnerFull) - assertProcessingStateDto(task.processingState, ResultState.Success, TaskStep.PoolSync, StepState.Success) + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, TaskStep.PoolSync, businessPartnerFull) + assertProcessingState(task.processingState, ResultState.Success, TaskStep.PoolSync, StepState.Success) // taskRetentionTimeout has been set; taskPendingTimeout has been reset - assertThat(task.processingState.taskPendingTimeout).isNull() - assertThat(task.processingState.taskRetentionTimeout).isCloseTo( + assertThat(task.processingState.pendingTimeout).isNull() + assertThat(task.processingState.retentionTimeout?.instant).isCloseTo( Instant.now().plus(taskConfigProperties.taskRetentionTimeout), WITHIN_ALLOWED_TIME_OFFSET ) // Can't resolve again! assertThatThrownBy { - goldenRecordTaskStateMachine.doResolveTaskToError(task, TaskStep.PoolSync, listOf(TaskErrorDto(TaskErrorType.Unspecified, "error"))) + goldenRecordTaskStateMachine.doResolveTaskToError( + task, + TaskStep.PoolSync, + listOf(TaskErrorDto(TaskErrorType.Unspecified, "error")) + ) }.isInstanceOf(BpdmIllegalStateException::class.java) } @@ -142,33 +168,34 @@ class GoldenRecordTaskStateMachineIT @Autowired constructor( * THEN expect the TaskProcessingState to walk through all the steps/states and taskModifiedAt to be updated */ @Test + @Transactional fun `walk through all UpdateFromPool steps`() { // new task - val task = initTask(TaskMode.UpdateFromPool) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Queued) - val modified0 = task.processingState.taskModifiedAt + val task = goldenRecordTaskStateMachine.initTask(TaskMode.UpdateFromPool, businessPartnerFull, gateRecord) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Queued) + val modified0 = task.updatedAt.instant // taskPendingTimeout has been set - assertThat(task.processingState.taskPendingTimeout).isCloseTo(Instant.now().plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) + assertThat(task.processingState.pendingTimeout?.instant).isCloseTo(Instant.now().plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) Thread.sleep(10) // reserve goldenRecordTaskStateMachine.doReserve(task) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Reserved) - val modified1 = task.processingState.taskModifiedAt + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Reserved) + val modified1 = task.updatedAt.instant assertThat(modified1).isAfter(modified0) Thread.sleep(10) // resolve - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, TaskStep.Clean, businessPartnerFull) - assertProcessingStateDto(task.processingState, ResultState.Success, TaskStep.Clean, StepState.Success) - val modified2 = task.processingState.taskModifiedAt + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, TaskStep.Clean, businessPartnerFull) + assertProcessingState(task.processingState, ResultState.Success, TaskStep.Clean, StepState.Success) + val modified2 = task.updatedAt.instant assertThat(modified2).isAfter(modified1) // taskRetentionTimeout has been set; taskPendingTimeout was reset - assertThat(task.processingState.taskPendingTimeout).isNull() - assertThat(task.processingState.taskRetentionTimeout).isCloseTo( + assertThat(task.processingState.pendingTimeout).isNull() + assertThat(task.processingState.retentionTimeout?.instant).isCloseTo( Instant.now().plus(taskConfigProperties.taskRetentionTimeout), WITHIN_ALLOWED_TIME_OFFSET ) @@ -181,16 +208,17 @@ class GoldenRecordTaskStateMachineIT @Autowired constructor( * THEN expect the TaskProcessingState to reach final state Error */ @Test + @Transactional fun `walk through steps and resolve with error`() { // new task - val task = initTask(TaskMode.UpdateFromPool) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Queued) + val task = goldenRecordTaskStateMachine.initTask(TaskMode.UpdateFromPool, businessPartnerFull, gateRecord) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Queued) // taskPendingTimeout has been set - assertThat(task.processingState.taskPendingTimeout).isCloseTo(Instant.now().plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) + assertThat(task.processingState.pendingTimeout?.instant).isCloseTo(Instant.now().plus(taskConfigProperties.taskPendingTimeout), WITHIN_ALLOWED_TIME_OFFSET) // reserve goldenRecordTaskStateMachine.doReserve(task) - assertProcessingStateDto(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Reserved) + assertProcessingState(task.processingState, ResultState.Pending, TaskStep.Clean, StepState.Reserved) // resolve with error val errors = listOf( @@ -198,12 +226,12 @@ class GoldenRecordTaskStateMachineIT @Autowired constructor( TaskErrorDto(TaskErrorType.Timeout, "Timeout") ) goldenRecordTaskStateMachine.doResolveTaskToError(task, TaskStep.Clean, errors) - assertProcessingStateDto(task.processingState, ResultState.Error, TaskStep.Clean, StepState.Error) - assertThat(task.processingState.errors).isEqualTo(errors) + assertProcessingState(task.processingState, ResultState.Error, TaskStep.Clean, StepState.Error) + assertThat(task.processingState.errors).usingRecursiveFieldByFieldElementComparator().isEqualTo(errors) // taskRetentionTimeout has been set; taskPendingTimeout was reset - assertThat(task.processingState.taskPendingTimeout).isNull() - assertThat(task.processingState.taskRetentionTimeout).isCloseTo( + assertThat(task.processingState.pendingTimeout).isNull() + assertThat(task.processingState.retentionTimeout?.instant).isCloseTo( Instant.now().plus(taskConfigProperties.taskRetentionTimeout), WITHIN_ALLOWED_TIME_OFFSET ) @@ -215,20 +243,13 @@ class GoldenRecordTaskStateMachineIT @Autowired constructor( // Can't resolve now! assertThatThrownBy { - goldenRecordTaskStateMachine.doResolveTaskToSuccess(task, TaskStep.Clean, businessPartnerFull) + goldenRecordTaskStateMachine.resolveTaskStepToSuccess(task, TaskStep.Clean, businessPartnerFull) }.isInstanceOf(BpdmIllegalStateException::class.java) } - private fun assertProcessingStateDto(processingState: TaskProcessingState, resultState: ResultState, step: TaskStep, stepState: StepState) { + private fun assertProcessingState(processingState: GoldenRecordTaskDb.ProcessingState, resultState: ResultState, step: TaskStep, stepState: StepState) { assertThat(processingState.resultState).isEqualTo(resultState) assertThat(processingState.step).isEqualTo(step) assertThat(processingState.stepState).isEqualTo(stepState) } - - private fun initTask(mode: TaskMode = TaskMode.UpdateFromSharingMember) = - GoldenRecordTask( - taskId = TASK_ID, - businessPartner = businessPartnerFull, - processingState = goldenRecordTaskStateMachine.initProcessingState(mode) - ) } diff --git a/bpdm-pool-api/pom.xml b/bpdm-pool-api/pom.xml index cb9706826..4307acf6c 100644 --- a/bpdm-pool-api/pom.xml +++ b/bpdm-pool-api/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-pool/pom.xml b/bpdm-pool/pom.xml index a6141e4c6..62cb7256c 100644 --- a/bpdm-pool/pom.xml +++ b/bpdm-pool/pom.xml @@ -31,7 +31,7 @@ org.eclipse.tractusx bpdm-parent - 6.1.0-SNAPSHOT + 6.1.0 diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepFetchAndReserveServiceTest.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepFetchAndReserveServiceTest.kt index 8c3e17078..74ed35924 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepFetchAndReserveServiceTest.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepFetchAndReserveServiceTest.kt @@ -21,13 +21,16 @@ package org.eclipse.tractusx.bpdm.pool.service import com.neovisionaries.i18n.CountryCode import org.assertj.core.api.Assertions.assertThat -import org.eclipse.tractusx.bpdm.common.dto.* +import org.eclipse.tractusx.bpdm.common.dto.AddressType import org.eclipse.tractusx.bpdm.pool.Application import org.eclipse.tractusx.bpdm.pool.api.client.PoolApiClient import org.eclipse.tractusx.bpdm.pool.repository.BpnRequestIdentifierRepository import org.eclipse.tractusx.bpdm.pool.service.TaskStepBuildService.CleaningError import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer -import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.* +import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.BusinessPartnerTestDataFactory +import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.copyWithBpnRequests +import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.copyWithLegalEntityIdentifiers +import org.eclipse.tractusx.bpdm.test.testdata.orchestrator.copyWithSiteMainAddress import org.eclipse.tractusx.bpdm.test.testdata.pool.PoolDataHelper import org.eclipse.tractusx.bpdm.test.testdata.pool.TestDataEnvironment import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers @@ -42,6 +45,7 @@ import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import java.time.Instant import java.time.temporal.ChronoUnit +import java.util.* @SpringBootTest( @@ -744,6 +748,7 @@ class TaskStepFetchAndReserveServiceTest @Autowired constructor( return listOf( TaskStepReservationEntryDto( taskId = taskId, + recordId = UUID.randomUUID().toString(), businessPartner = businessPartner ) ) @@ -754,6 +759,7 @@ class TaskStepFetchAndReserveServiceTest @Autowired constructor( return businessPartners.map { TaskStepReservationEntryDto( taskId = it.legalEntity.bpnReference.referenceValue!!, + recordId = UUID.randomUUID().toString(), businessPartner = it ) } diff --git a/charts/bpdm/CHANGELOG.md b/charts/bpdm/CHANGELOG.md index 6bb3332a6..1847556ff 100644 --- a/charts/bpdm/CHANGELOG.md +++ b/charts/bpdm/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), +## [5.1.0] - 2024-07-15 + +### Changed + +- Increase appversion to 6.1.0 +- update BPDM Pool Chart to version 7.1.0 +- update BPDM Gate Chart to version 6.1.0 +- update BPDM Orchestrator Chart to version 3.1.0 +- update BPDM Cleaning Service Dummy Chart to version 3.1.0 +- update BPDM Bridge Chart to version 3.1.0 + ## [5.0.1] - 2024-05-27 ### Changed diff --git a/charts/bpdm/Chart.yaml b/charts/bpdm/Chart.yaml index 2bc4124f7..66688277d 100644 --- a/charts/bpdm/Chart.yaml +++ b/charts/bpdm/Chart.yaml @@ -22,8 +22,8 @@ apiVersion: v2 name: bpdm type: application description: A Helm chart for Kubernetes that deploys the BPDM applications -version: 5.1.0-SNAPSHOT -appVersion: "6.1.0-SNAPSHOT" +version: 5.1.0 +appVersion: "6.1.0" home: https://github.com/eclipse-tractusx/bpdm sources: - https://github.com/eclipse-tractusx/bpdm @@ -33,28 +33,23 @@ maintainers: dependencies: - name: bpdm-gate - version: 6.1.0-SNAPSHOT + version: 6.1.0 alias: bpdm-gate condition: bpdm-gate.enabled - repository: "file://./charts/bpdm-gate" - name: bpdm-pool - version: 7.1.0-SNAPSHOT + version: 7.1.0 alias: bpdm-pool condition: bpdm-pool.enabled - repository: "file://./charts/bpdm-pool" - name: bpdm-cleaning-service-dummy - version: 3.1.0-SNAPSHOT + version: 3.1.0 alias: bpdm-cleaning-service-dummy condition: bpdm-cleaning-service-dummy.enabled - repository: "file://./charts/bpdm-cleaning-service-dummy" - name: bpdm-orchestrator - version: 3.1.0-SNAPSHOT + version: 3.1.0 alias: bpdm-orchestrator condition: bpdm-orchestrator.enabled - repository: "file://./charts/bpdm-orchestrator" - name: bpdm-common version: 1.0.1 - repository: "file://./charts/bpdm-common" - name: postgresql version: 12.12.10 repository: https://charts.bitnami.com/bitnami diff --git a/charts/bpdm/charts/bpdm-cleaning-service-dummy/CHANGELOG.md b/charts/bpdm/charts/bpdm-cleaning-service-dummy/CHANGELOG.md index 5f2c45fae..d75ff538c 100644 --- a/charts/bpdm/charts/bpdm-cleaning-service-dummy/CHANGELOG.md +++ b/charts/bpdm/charts/bpdm-cleaning-service-dummy/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), +## [3.1.0] - 2024-07-15 + +### Changed + +- Increase appversion to 6.1.0 +- Increased range of CPU request and limit +- Reduced initial delay of startup probe +- Value for delaying startup of application container. + This decreases crashes when waiting for Keycloak and Postgres dependencies to be up. + ## [3.0.1] - 2024-05-27 ### Changed diff --git a/charts/bpdm/charts/bpdm-cleaning-service-dummy/Chart.yaml b/charts/bpdm/charts/bpdm-cleaning-service-dummy/Chart.yaml index 96d0d0893..721a1efb5 100644 --- a/charts/bpdm/charts/bpdm-cleaning-service-dummy/Chart.yaml +++ b/charts/bpdm/charts/bpdm-cleaning-service-dummy/Chart.yaml @@ -21,8 +21,8 @@ apiVersion: v2 type: application name: bpdm-cleaning-service-dummy -appVersion: "6.1.0-SNAPSHOT" -version: 3.1.0-SNAPSHOT +appVersion: "6.1.0" +version: 3.1.0 description: A Helm chart for deploying the BPDM cleaning service home: https://eclipse-tractusx.github.io/docs/kits/Business%20Partner%20Kit/Adoption%20View sources: diff --git a/charts/bpdm/charts/bpdm-cleaning-service-dummy/values.yaml b/charts/bpdm/charts/bpdm-cleaning-service-dummy/values.yaml index b9c98c133..96f15d630 100644 --- a/charts/bpdm/charts/bpdm-cleaning-service-dummy/values.yaml +++ b/charts/bpdm/charts/bpdm-cleaning-service-dummy/values.yaml @@ -59,7 +59,7 @@ autoscaling: resources: limits: - cpu: 500m + cpu: 1000m memory: 1Gi requests: cpu: 100m @@ -80,12 +80,14 @@ affinity: operator: DoesNotExist topologyKey: kubernetes.io/hostname +startupDelaySeconds: 90 + livenessProbe: httpGet: path: "/actuator/health/liveness" port: 8084 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 @@ -95,7 +97,7 @@ readinessProbe: path: "/actuator/health/readiness" port: 8084 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 @@ -105,9 +107,9 @@ startupProbe: path: "/actuator/health/readiness" port: 8084 scheme: HTTP - initialDelaySeconds: 60 + initialDelaySeconds: 30 failureThreshold: 40 - periodSeconds: 30 + periodSeconds: 5 timeoutSeconds: 1 successThreshold: 1 diff --git a/charts/bpdm/charts/bpdm-common/templates/_deployment.tpl b/charts/bpdm/charts/bpdm-common/templates/_deployment.tpl index 55942f519..0cd905224 100644 --- a/charts/bpdm/charts/bpdm-common/templates/_deployment.tpl +++ b/charts/bpdm/charts/bpdm-common/templates/_deployment.tpl @@ -80,6 +80,10 @@ spec: readOnly: true - mountPath: /tmp name: cache + initContainers: + - name: startup-delay + image: busybox:1.28 + command: ['sh', '-c', "sleep {{ $.Values.startupDelaySeconds }}"] {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/bpdm/charts/bpdm-gate/CHANGELOG.md b/charts/bpdm/charts/bpdm-gate/CHANGELOG.md index 391f616ae..db76bfc9c 100644 --- a/charts/bpdm/charts/bpdm-gate/CHANGELOG.md +++ b/charts/bpdm/charts/bpdm-gate/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), +## [6.1.0] - 2024-07-15 + +### Changed + +- Increase appversion to 6.1.0 +- Increased range of CPU request and limit +- Reduced initial delay of startup probe +- Value for delaying startup of application container. + This decreases crashes when waiting for Keycloak and Postgres dependencies to be up. + ## [6.0.1] - 2024-05-27 ### Changed diff --git a/charts/bpdm/charts/bpdm-gate/Chart.yaml b/charts/bpdm/charts/bpdm-gate/Chart.yaml index 9cdc48c4c..581ffc35a 100644 --- a/charts/bpdm/charts/bpdm-gate/Chart.yaml +++ b/charts/bpdm/charts/bpdm-gate/Chart.yaml @@ -21,8 +21,8 @@ apiVersion: v2 type: application name: bpdm-gate -appVersion: "6.1.0-SNAPSHOT" -version: 6.1.0-SNAPSHOT +appVersion: "6.1.0" +version: 6.1.0 description: A Helm chart for deploying the BPDM gate service home: https://eclipse-tractusx.github.io/docs/kits/Business%20Partner%20Kit/Adoption%20View sources: diff --git a/charts/bpdm/charts/bpdm-gate/values.yaml b/charts/bpdm/charts/bpdm-gate/values.yaml index 6285ec45f..df93bf0ad 100644 --- a/charts/bpdm/charts/bpdm-gate/values.yaml +++ b/charts/bpdm/charts/bpdm-gate/values.yaml @@ -66,7 +66,7 @@ resources: cpu: 1000m memory: 1Gi requests: - cpu: 200m + cpu: 100m memory: 1Gi nodeSelector: {} @@ -84,12 +84,14 @@ affinity: operator: DoesNotExist topologyKey: kubernetes.io/hostname +startupDelaySeconds: 90 + livenessProbe: httpGet: path: "/actuator/health/liveness" port: 8081 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 @@ -99,7 +101,7 @@ readinessProbe: path: "/actuator/health/readiness" port: 8081 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 @@ -109,9 +111,9 @@ startupProbe: path: "/actuator/health/readiness" port: 8081 scheme: HTTP - initialDelaySeconds: 60 + initialDelaySeconds: 30 failureThreshold: 40 - periodSeconds: 30 + periodSeconds: 5 timeoutSeconds: 1 successThreshold: 1 diff --git a/charts/bpdm/charts/bpdm-orchestrator/CHANGELOG.md b/charts/bpdm/charts/bpdm-orchestrator/CHANGELOG.md index a2bab3a06..b723b953b 100644 --- a/charts/bpdm/charts/bpdm-orchestrator/CHANGELOG.md +++ b/charts/bpdm/charts/bpdm-orchestrator/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file. The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), +## [3.1.0] - 2024-07-15 + +### Added + +- Postgres dependency + +### Changed + +- Increase appversion to 6.1.0 +- Increased range of CPU request and limit +- Reduced initial delay of startup probe +- Value for delaying startup of application container. + This decreases crashes when waiting for Keycloak and Postgres dependencies to be up. + ## [3.0.1] - 2024-05-27 ### Changed diff --git a/charts/bpdm/charts/bpdm-orchestrator/Chart.yaml b/charts/bpdm/charts/bpdm-orchestrator/Chart.yaml index b5fbd2675..3d7b28e18 100644 --- a/charts/bpdm/charts/bpdm-orchestrator/Chart.yaml +++ b/charts/bpdm/charts/bpdm-orchestrator/Chart.yaml @@ -21,8 +21,8 @@ apiVersion: v2 type: application name: bpdm-orchestrator -appVersion: "6.1.0-SNAPSHOT" -version: 3.1.0-SNAPSHOT +appVersion: "6.1.0" +version: 3.1.0 description: A Helm chart for deploying the BPDM Orchestrator service home: https://eclipse-tractusx.github.io/docs/kits/Business%20Partner%20Kit/Adoption%20View sources: @@ -31,6 +31,11 @@ dependencies: - name: bpdm-common version: 1.0.1 repository: "file://../bpdm-common" + - name: postgresql + version: 12.12.10 + repository: https://charts.bitnami.com/bitnami + alias: postgres + condition: postgres.enabled maintainers: - name: Nico Koprowski - name: Fabio D. Mota diff --git a/charts/bpdm/charts/bpdm-orchestrator/templates/configMap.yaml b/charts/bpdm/charts/bpdm-orchestrator/templates/configMap.yaml index 8b70f22c1..131829c4d 100644 --- a/charts/bpdm/charts/bpdm-orchestrator/templates/configMap.yaml +++ b/charts/bpdm/charts/bpdm-orchestrator/templates/configMap.yaml @@ -18,4 +18,9 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################ -{{ include "bpdm-common.configMap" (dict "context" $) -}} +{{ include "bpdm-common.configMap" (dict "context" $ "defaultValues" "bpdm-orchestrator.configmapDefaults") -}} +{{- define "bpdm-orchestrator.configmapDefaults" -}} +bpdm: + datasource: + host: {{ include "bpdm.postgresDependency" . }} +{{- end -}} diff --git a/charts/bpdm/charts/bpdm-orchestrator/values.yaml b/charts/bpdm/charts/bpdm-orchestrator/values.yaml index 5401e35da..b63deeea3 100644 --- a/charts/bpdm/charts/bpdm-orchestrator/values.yaml +++ b/charts/bpdm/charts/bpdm-orchestrator/values.yaml @@ -65,7 +65,7 @@ ingress: resources: limits: - cpu: 500m + cpu: 1000m memory: 1Gi requests: cpu: 100m @@ -86,12 +86,14 @@ affinity: operator: DoesNotExist topologyKey: kubernetes.io/hostname +startupDelaySeconds: 90 + livenessProbe: httpGet: path: "/actuator/health/liveness" port: 8085 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 @@ -101,7 +103,7 @@ readinessProbe: path: "/actuator/health/readiness" port: 8085 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 @@ -111,18 +113,32 @@ startupProbe: path: "/actuator/health/readiness" port: 8085 scheme: HTTP - initialDelaySeconds: 60 + initialDelaySeconds: 30 failureThreshold: 40 - periodSeconds: 30 + periodSeconds: 5 timeoutSeconds: 1 successThreshold: 1 # Used to overwrite the default property values of the application configuration applicationConfig: - + bpdm: + datasource: + # If empty sets kubernetes postgres service with same release name + host: # Used to overwrite the secret property values of the application configuration applicationSecrets: + spring: + dataSource: + # Change this password for production + password: &postgresPass bpdm + +postgres: + enabled: true + auth: + database: bpdm + username: bpdm + password: *postgresPass diff --git a/charts/bpdm/charts/bpdm-pool/CHANGELOG.md b/charts/bpdm/charts/bpdm-pool/CHANGELOG.md index ea10229e1..9c7f77c70 100644 --- a/charts/bpdm/charts/bpdm-pool/CHANGELOG.md +++ b/charts/bpdm/charts/bpdm-pool/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), +## [7.1.0] - 2024-07-15 + +### Changed + +- Increase appversion to 6.1.0 +- Increased range of CPU request and limit +- Reduced initial delay of startup probe +- Value for delaying startup of application container. + This decreases crashes when waiting for Keycloak and Postgres dependencies to be up. + ## [7.0.1] - 2024-05-27 ### Changed diff --git a/charts/bpdm/charts/bpdm-pool/Chart.yaml b/charts/bpdm/charts/bpdm-pool/Chart.yaml index 9eb0e23bb..be8179911 100644 --- a/charts/bpdm/charts/bpdm-pool/Chart.yaml +++ b/charts/bpdm/charts/bpdm-pool/Chart.yaml @@ -21,8 +21,8 @@ apiVersion: v2 type: application name: bpdm-pool -appVersion: "6.1.0-SNAPSHOT" -version: 7.1.0-SNAPSHOT +appVersion: "6.1.0" +version: 7.1.0 description: A Helm chart for deploying the BPDM pool service home: https://eclipse-tractusx.github.io/docs/kits/Business%20Partner%20Kit/Adoption%20View sources: diff --git a/charts/bpdm/charts/bpdm-pool/values.yaml b/charts/bpdm/charts/bpdm-pool/values.yaml index 129a62325..400208f80 100644 --- a/charts/bpdm/charts/bpdm-pool/values.yaml +++ b/charts/bpdm/charts/bpdm-pool/values.yaml @@ -66,7 +66,7 @@ resources: cpu: 1000m memory: 1Gi requests: - cpu: 300m + cpu: 100m memory: 1Gi nodeSelector: {} @@ -84,13 +84,15 @@ affinity: operator: DoesNotExist topologyKey: kubernetes.io/hostname +startupDelaySeconds: 90 + livenessProbe: httpGet: path: "/actuator/health/liveness" port: 8080 scheme: HTTP failureThreshold: 5 - initialDelaySeconds: 120 + initialDelaySeconds: 5 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 @@ -100,9 +102,9 @@ readinessProbe: path: "/actuator/health/readiness" port: 8080 scheme: HTTP - failureThreshold: 3 + failureThreshold: 5 initialDelaySeconds: 5 - periodSeconds: 5 + periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 @@ -111,9 +113,9 @@ startupProbe: path: "/actuator/health/readiness" port: 8080 scheme: HTTP - initialDelaySeconds: 60 + initialDelaySeconds: 30 failureThreshold: 40 - periodSeconds: 30 + periodSeconds: 5 timeoutSeconds: 1 successThreshold: 1 diff --git a/charts/bpdm/values.yaml b/charts/bpdm/values.yaml index 0fc6330f7..0b2b98938 100644 --- a/charts/bpdm/values.yaml +++ b/charts/bpdm/values.yaml @@ -70,12 +70,14 @@ bpdm-cleaning-service-dummy: bpdm-orchestrator: enabled: true + postgres: + enabled: false + fullnameOverride: bpdm-postgres applicationConfig: bpdm: security: auth-server-url: http://bpdm-keycloak - postgres: enabled: true fullnameOverride: bpdm-postgres diff --git a/pom.xml b/pom.xml index 910c344f3..0db6c3bc2 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ bpdm-parent Business Partner Data Management Parent Parent pom of Business Partner Data Management - 6.1.0-SNAPSHOT + 6.1.0 pom @@ -164,6 +164,12 @@ kotlin-logging-jvm ${kotlinlogging.version} + + + org.apache.tomcat.embed + tomcat-embed-core + 10.1.25 +