diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt index 6d092a936..c12ec5afd 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt @@ -92,14 +92,14 @@ abstract class BaseSyncRecordService, SYNC_RECORD : BaseSync } @Transactional(isolation = Isolation.SERIALIZABLE) - open fun setSynchronizationSuccess(type: SYNC_TYPE): SYNC_RECORD { + open fun setSynchronizationSuccess(type: SYNC_TYPE, finishedAt: Instant? = null): SYNC_RECORD { val record = getOrCreateRecord(type) if (record.status != SyncStatus.RUNNING) throw BpdmSyncStateException("Synchronization of type ${record.type} can't switch from state ${record.status} to ${SyncStatus.SUCCESS}.") logger.debug { "Set sync of type ${record.type} to status ${SyncStatus.SUCCESS}" } - record.finishedAt = Instant.now().truncatedTo(ChronoUnit.MICROS) + record.finishedAt = finishedAt ?: Instant.now().truncatedTo(ChronoUnit.MICROS) record.progress = 1f record.status = SyncStatus.SUCCESS record.errorDetails = null diff --git a/bpdm-gate/pom.xml b/bpdm-gate/pom.xml index 0e6fca00c..b604cf260 100644 --- a/bpdm-gate/pom.xml +++ b/bpdm-gate/pom.xml @@ -43,6 +43,10 @@ ${project.groupId} bpdm-gate-api + + ${project.groupId} + bpdm-pool-api + org.jetbrains.kotlin kotlin-stdlib diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/OrchestratorClientConfig.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/ClientsConfig.kt similarity index 76% rename from bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/OrchestratorClientConfig.kt rename to bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/ClientsConfig.kt index 817bb3c6f..fd7e60b77 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/OrchestratorClientConfig.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/ClientsConfig.kt @@ -19,6 +19,8 @@ package org.eclipse.tractusx.bpdm.gate.config +import org.eclipse.tractusx.bpdm.pool.api.client.PoolApiClient +import org.eclipse.tractusx.bpdm.pool.api.client.PoolClientImpl import org.eclipse.tractusx.orchestrator.api.client.OrchestrationApiClient import org.eclipse.tractusx.orchestrator.api.client.OrchestrationApiClientImpl import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty @@ -37,7 +39,7 @@ import java.util.function.Consumer @Configuration -class OrchestratorClientConfig { +class ClientsConfig { // Orchestrator-Client without authentication @Bean @@ -51,6 +53,22 @@ class OrchestratorClientConfig { return OrchestrationApiClientImpl { webClientBuilder(url).build() } } + @Bean + @ConditionalOnProperty( + value = ["bpdm.gate-security.pool-security-enabled"], + havingValue = "false", + matchIfMissing = true + ) + fun poolClientNoAuth(poolConfigProperties: PoolConfigProperties): PoolApiClient { + val url = poolConfigProperties.baseUrl + return PoolClientImpl { + WebClient.builder() + .baseUrl(url) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build() + } + } + + @Bean @ConditionalOnProperty( @@ -72,6 +90,25 @@ class OrchestratorClientConfig { } } + @Bean + @ConditionalOnProperty( + value = ["bpdm.pool.security-enabled"], + havingValue = "true" + ) + fun poolClientWithAuth( + poolConfigProperties: PoolConfigProperties, + clientRegistrationRepository: ClientRegistrationRepository, + authorizedClientService: OAuth2AuthorizedClientService + ): PoolApiClient { + val url = poolConfigProperties.baseUrl + val clientRegistrationId = poolConfigProperties.oauth2ClientRegistration + ?: throw IllegalArgumentException("bpdm.pool.oauth2-client-registration is required if bpdm.pool.security-enabled is set") + return PoolClientImpl { + webClientBuilder(url) + .apply(oauth2Configuration(clientRegistrationRepository, authorizedClientService, clientRegistrationId)) + .build() + } + } private fun webClientBuilder(url: String) = WebClient.builder() diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PoolConfigProperties.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PoolConfigProperties.kt index 4d0e12d84..59f382920 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PoolConfigProperties.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PoolConfigProperties.kt @@ -24,5 +24,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties(prefix = "bpdm.client.pool") data class PoolConfigProperties( - val baseUrl: String = "http://localhost:8080/api/catena", + val baseUrl: String = "http://localhost:8080", + val searchChangelogPageSize: Int = 100, + val securityEnabled: Boolean = false, + val oauth2ClientRegistration: String? ) diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SyncRecord.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SyncRecord.kt new file mode 100644 index 000000000..b403b4a8d --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SyncRecord.kt @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 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.gate.entity + +import jakarta.persistence.* +import org.eclipse.tractusx.bpdm.common.model.BaseEntity +import org.eclipse.tractusx.bpdm.common.model.BaseSyncRecord +import org.eclipse.tractusx.bpdm.common.model.SyncStatus +import java.time.Instant + +@Entity +@Table(name = "sync_records") +class SyncRecord( + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false, unique = true) + override var type: SyncType, + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + override var status: SyncStatus, + + @Column(name = "from_time", nullable = false) + override var fromTime: Instant, + + @Column(name = "progress", nullable = false) + override var progress: Float = 0f, + + @Column(name = "count", nullable = false) + override var count: Int = 0, + + @Column(name = "status_details") + override var errorDetails: String? = null, + + @Column(name = "save_state") + override var errorSave: String? = null, + + @Column(name = "started_at") + override var startedAt: Instant? = null, + + @Column(name = "finished_at") + override var finishedAt: Instant? = null, + + ) : BaseEntity(), BaseSyncRecord diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SyncType.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SyncType.kt new file mode 100644 index 000000000..15d3e1efc --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/SyncType.kt @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 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.gate.entity + +enum class SyncType { + POOL_TO_GATE_OUTPUT +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SyncRecordRepository.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SyncRecordRepository.kt new file mode 100644 index 000000000..eb7416899 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SyncRecordRepository.kt @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 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.gate.repository + +import org.eclipse.tractusx.bpdm.gate.entity.SyncRecord +import org.eclipse.tractusx.bpdm.gate.entity.SyncType +import org.springframework.data.repository.CrudRepository + +interface SyncRecordRepository : CrudRepository { + + fun findByType(type: SyncType): SyncRecord? +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/generic/BusinessPartnerRepository.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/generic/BusinessPartnerRepository.kt index 2fe4e1a98..f2ef3911c 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/generic/BusinessPartnerRepository.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/generic/BusinessPartnerRepository.kt @@ -24,9 +24,12 @@ import org.eclipse.tractusx.bpdm.gate.entity.generic.BusinessPartner import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.CrudRepository +import org.springframework.data.repository.query.Param import org.springframework.stereotype.Repository + @Repository interface BusinessPartnerRepository : JpaRepository, CrudRepository { @@ -35,4 +38,13 @@ interface BusinessPartnerRepository : JpaRepository, Crud fun findByStageAndExternalIdIn(stage: StageType, externalId: Collection, pageable: Pageable): Page fun findByStage(stage: StageType, pageable: Pageable): Page + + @Query("SELECT e FROM BusinessPartner e WHERE e.stage = :stage AND (e.bpnL IN :bpnL OR e.bpnS IN :bpnS OR e.bpnA IN :bpnA)") + fun findByStageAndBpnLInOrBpnSInOrBpnAIn( + @Param("stage") stage: StageType?, + @Param("bpnL") bpnLList: List?, + @Param("bpnS") bpnSList: List?, + @Param("bpnA") bpnAList: List? + ): Set + } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt index fd1410f15..6a69a8338 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt @@ -19,6 +19,7 @@ package org.eclipse.tractusx.bpdm.gate.service +import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerType import org.eclipse.tractusx.bpdm.common.dto.GeoCoordinateDto import org.eclipse.tractusx.bpdm.common.exception.BpdmNullMappingException import org.eclipse.tractusx.bpdm.common.model.StageType @@ -121,7 +122,7 @@ class BusinessPartnerMappings { bpnS = dto.siteBpn, bpnA = dto.addressBpn, parentId = null, - parentType = null, + parentType = BusinessPartnerType.GENERIC, postalAddress = toPostalAddress(dto.postalAddress) ) } 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 5e50b1add..22cb562b1 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 @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.gate.service import mu.KotlinLogging import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerType +import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest import org.eclipse.tractusx.bpdm.common.dto.response.PageDto import org.eclipse.tractusx.bpdm.common.model.StageType import org.eclipse.tractusx.bpdm.common.service.toPageDto @@ -33,12 +34,16 @@ import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequ import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerOutputRequest import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerOutputDto +import org.eclipse.tractusx.bpdm.gate.config.PoolConfigProperties import org.eclipse.tractusx.bpdm.gate.entity.ChangelogEntry +import org.eclipse.tractusx.bpdm.gate.entity.SyncType import org.eclipse.tractusx.bpdm.gate.entity.generic.* import org.eclipse.tractusx.bpdm.gate.exception.BpdmMissingStageException import org.eclipse.tractusx.bpdm.gate.repository.ChangelogRepository import org.eclipse.tractusx.bpdm.gate.repository.SharingStateRepository import org.eclipse.tractusx.bpdm.gate.repository.generic.BusinessPartnerRepository +import org.eclipse.tractusx.bpdm.pool.api.client.PoolApiClient +import org.eclipse.tractusx.bpdm.pool.api.model.request.ChangelogSearchRequest import org.eclipse.tractusx.orchestrator.api.client.OrchestrationApiClient import org.eclipse.tractusx.orchestrator.api.model.* import org.springframework.data.domain.Page @@ -46,6 +51,7 @@ import org.springframework.data.domain.PageRequest import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.eclipse.tractusx.bpdm.pool.api.model.ChangelogType as PoolChangeLogType @Service class BusinessPartnerService( @@ -55,7 +61,10 @@ class BusinessPartnerService( private val changelogRepository: ChangelogRepository, private val orchestrationApiClient: OrchestrationApiClient, private val orchestratorMappings: OrchestratorMappings, - private val sharingStateRepository: SharingStateRepository + private val sharingStateRepository: SharingStateRepository, + private val poolClient: PoolApiClient, + private val syncRecordService: SyncRecordService, + private val poolConfigProperties: PoolConfigProperties ) { private val logger = KotlinLogging.logger { } @@ -98,7 +107,7 @@ class BusinessPartnerService( val partners = resolutionResults.map { it.businessPartner } val orchestratorBusinessPartnersDto = resolutionResults.map { orchestratorMappings.toBusinessPartnerGenericDto(it.businessPartner) } - val taskIds = createGoldenRecordTasks(orchestratorBusinessPartnersDto).createdTasks.map { it.taskId } + val taskIds = createGoldenRecordTasks(TaskMode.UpdateFromSharingMember, orchestratorBusinessPartnersDto).createdTasks.map { it.taskId } val pendingRequests = partners.zip(taskIds) .map { (partner, taskId) -> @@ -293,10 +302,10 @@ class BusinessPartnerService( } - private fun createGoldenRecordTasks(orchestratorBusinessPartnersDto: List): TaskCreateResponse { + private fun createGoldenRecordTasks(mode: TaskMode, orchestratorBusinessPartnersDto: List): TaskCreateResponse { return orchestrationApiClient.goldenRecordTasks.createTasks( TaskCreateRequest( - TaskMode.UpdateFromSharingMember, orchestratorBusinessPartnersDto + mode, orchestratorBusinessPartnersDto ) ) } @@ -340,4 +349,51 @@ class BusinessPartnerService( } + @Scheduled(cron = "\${cleaningService.pollingCron:-}", zone = "UTC") + @Transactional + fun requestCleaningTaskForGateOutputIfPoolBpnHasChanges() { + + val poolPaginationRequest = PaginationRequest(0, poolConfigProperties.searchChangelogPageSize) + + val syncRecord = syncRecordService.getOrCreateRecord(SyncType.POOL_TO_GATE_OUTPUT) + + val changelogSearchRequest = ChangelogSearchRequest(syncRecord.finishedAt) + + + val poolChangelogEntries = poolClient.changelogs.getChangelogEntries(changelogSearchRequest, poolPaginationRequest) + val poolUpdatedEntries = poolChangelogEntries.content.filter { it.changelogType == PoolChangeLogType.UPDATE } + + + val bpnA = + poolUpdatedEntries.filter { it.businessPartnerType == BusinessPartnerType.ADDRESS }.map { it.bpn } + val bpnL = poolUpdatedEntries.filter { it.businessPartnerType == BusinessPartnerType.LEGAL_ENTITY } + .map { it.bpn } + val bpnS = + poolUpdatedEntries.filter { it.businessPartnerType == BusinessPartnerType.SITE }.map { it.bpn } + + val gateOutputEntries = businessPartnerRepository.findByStageAndBpnLInOrBpnSInOrBpnAIn(StageType.Output, bpnL, bpnS, bpnA) + + val businessPartnerGenericDtoList = gateOutputEntries.map { bp -> + orchestratorMappings.toBusinessPartnerGenericDto(bp) + } + + val taskIds = createGoldenRecordTasks(TaskMode.UpdateFromPool, businessPartnerGenericDtoList).createdTasks.map { it.taskId } + + val pendingRequests = gateOutputEntries.zip(taskIds) + .map { (partner, taskId) -> + SharingStateService.PendingRequest( + SharingStateService.SharingStateIdentifierDto( + partner.externalId, + partner.parentType ?: BusinessPartnerType.GENERIC + ), taskId + ) + } + sharingStateService.setPending(pendingRequests) + + if (poolUpdatedEntries.isNotEmpty()) { + syncRecordService.setSynchronizationStart(SyncType.POOL_TO_GATE_OUTPUT) + syncRecordService.setSynchronizationSuccess(SyncType.POOL_TO_GATE_OUTPUT, poolUpdatedEntries.last().timestamp) + } + } + } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SyncRecordService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SyncRecordService.kt new file mode 100644 index 000000000..0a11a1438 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/SyncRecordService.kt @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 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.gate.service + +import mu.KotlinLogging +import org.eclipse.tractusx.bpdm.common.model.SyncStatus +import org.eclipse.tractusx.bpdm.common.service.BaseSyncRecordService +import org.eclipse.tractusx.bpdm.gate.entity.SyncRecord +import org.eclipse.tractusx.bpdm.gate.entity.SyncType +import org.eclipse.tractusx.bpdm.gate.repository.SyncRecordRepository +import org.springframework.stereotype.Service +import java.time.Instant + +@Service +class SyncRecordService( + private val syncRecordRepository: SyncRecordRepository +) : BaseSyncRecordService() { + + override val logger = KotlinLogging.logger { } + + override fun newSyncRecord(type: SyncType, initialFromTime: Instant) = + SyncRecord( + type = type, + status = SyncStatus.NOT_SYNCED, + fromTime = initialFromTime + ) + + override fun save(record: SyncRecord) = + syncRecordRepository.save(record) + + override fun findByType(type: SyncType) = + syncRecordRepository.findByType(type) + +} \ No newline at end of file diff --git a/bpdm-gate/src/main/resources/application.yml b/bpdm-gate/src/main/resources/application.yml index bd0720bbd..190272a80 100644 --- a/bpdm-gate/src/main/resources/application.yml +++ b/bpdm-gate/src/main/resources/application.yml @@ -45,7 +45,7 @@ bpdm: base-url: http://localhost:8085 security-enabled: false pool: - base-url: http://localhost:8080/api/catena + base-url: http://localhost:8080 security-enabled: false # No security on default diff --git a/bpdm-gate/src/main/resources/db/migration/V4_1_0_3__create_table_sync_record.sql b/bpdm-gate/src/main/resources/db/migration/V4_1_0_3__create_table_sync_record.sql new file mode 100644 index 000000000..885b50c96 --- /dev/null +++ b/bpdm-gate/src/main/resources/db/migration/V4_1_0_3__create_table_sync_record.sql @@ -0,0 +1,18 @@ +CREATE TABLE sync_records ( + id int8 NOT NULL, + uuid uuid NOT NULL, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL, + "type" varchar(255) NOT NULL, + status varchar(255) NOT NULL, + progress float8 NOT NULL, + count int4 NOT NULL, + status_details varchar(255) NULL, + save_state varchar(255) NULL, + started_at timestamptz NULL, + finished_at timestamptz NULL, + from_time timestamptz NOT NULL DEFAULT '1970-01-01 08:00:00'::timestamp without time zone, + CONSTRAINT pk_sync_records PRIMARY KEY (id), + CONSTRAINT uc_sync_records_type UNIQUE (type), + CONSTRAINT uc_sync_records_uuid UNIQUE (uuid) +); \ No newline at end of file diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt index 2ce4a00b3..4e859e06c 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt @@ -26,6 +26,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension import org.assertj.core.api.Assertions import org.eclipse.tractusx.bpdm.common.dto.* import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest +import org.eclipse.tractusx.bpdm.common.dto.response.PageDto import org.eclipse.tractusx.bpdm.common.exception.BpdmNullMappingException import org.eclipse.tractusx.bpdm.gate.api.client.GateClient import org.eclipse.tractusx.bpdm.gate.api.exception.BusinessPartnerSharingError @@ -41,6 +42,8 @@ import org.eclipse.tractusx.bpdm.gate.api.model.response.SharingStateDto import org.eclipse.tractusx.bpdm.gate.entity.generic.BusinessPartner import org.eclipse.tractusx.bpdm.gate.service.BusinessPartnerService import org.eclipse.tractusx.bpdm.gate.util.* +import org.eclipse.tractusx.bpdm.pool.api.model.ChangelogType +import org.eclipse.tractusx.bpdm.pool.api.model.response.ChangelogEntryVerboseDto import org.eclipse.tractusx.orchestrator.api.model.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -71,16 +74,21 @@ class BusinessPartnerControllerIT @Autowired constructor( companion object { const val ORCHESTRATOR_CREATE_TASKS_URL = "/api/golden-record-tasks" const val ORCHESTRATOR_SEARCH_TASK_STATES_URL = "/api/golden-record-tasks/state/search" + const val POOL_API_SEARCH_CHANGE_LOG_URL = "/api/catena/business-partners/changelog/search" @JvmField @RegisterExtension - val gateWireMockServer: WireMockExtension = WireMockExtension.newInstance() - .options(WireMockConfiguration.wireMockConfig().dynamicPort()) - .build() + val gateWireMockServer: 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") { gateWireMockServer.baseUrl() } } } @@ -89,8 +97,8 @@ class BusinessPartnerControllerIT @Autowired constructor( fun beforeEach() { testHelpers.truncateDbTables() gateWireMockServer.resetAll() + poolWireMockServer.resetAll() this.mockOrchestratorApi() - this.mockOrchestratorApiCleaned() } @Test @@ -444,7 +452,7 @@ class BusinessPartnerControllerIT @Autowired constructor( listOf( TaskClientStateDto( taskId = "0", - businessPartnerResult = BusinessPartnerGenericMockValues.businessPartner1, + businessPartnerResult = BusinessPartnerGenericValues.businessPartner1, processingState = TaskProcessingStateDto( resultState = ResultState.Success, step = TaskStep.CleanAndSync, @@ -474,6 +482,28 @@ class BusinessPartnerControllerIT @Autowired constructor( ) ) + gateWireMockServer.stubFor( + WireMock.post(WireMock.urlPathEqualTo(ORCHESTRATOR_SEARCH_TASK_STATES_URL)).willReturn( + WireMock.okJson(objectMapper.writeValueAsString(taskStateResponse)) + ) + ) + } + private fun mockOrchestratorApiCleanedResponseSizeOne() { + val taskStateResponse = TaskStateResponse( + listOf( + TaskClientStateDto( + taskId = "0", businessPartnerResult = BusinessPartnerGenericValues.businessPartner1, processingState = TaskProcessingStateDto( + resultState = ResultState.Success, + step = TaskStep.CleanAndSync, + stepState = StepState.Queued, + errors = emptyList(), + createdAt = Instant.now(), + modifiedAt = Instant.now(), + timeout = Instant.now() + ) + ) + ) + ) gateWireMockServer.stubFor( WireMock.post(WireMock.urlPathEqualTo(ORCHESTRATOR_SEARCH_TASK_STATES_URL)) .willReturn( @@ -481,6 +511,43 @@ class BusinessPartnerControllerIT @Autowired constructor( ) ) } + private fun mockPoolApiGetChangeLogs() { + + val poolChangelogEntries = PageDto( + totalElements = 3, + totalPages = 1, + page = 0, + contentSize = 3, + content = listOf( + ChangelogEntryVerboseDto( + bpn = BusinessPartnerNonVerboseValues.bpOutputRequestCleaned.legalEntityBpn!!, + businessPartnerType = BusinessPartnerType.LEGAL_ENTITY, + timestamp = Instant.now(), + changelogType = ChangelogType.UPDATE + ), + ChangelogEntryVerboseDto( + bpn = BusinessPartnerNonVerboseValues.bpOutputRequestCleaned.addressBpn!!, + businessPartnerType = BusinessPartnerType.ADDRESS, + timestamp = Instant.now(), + changelogType = ChangelogType.UPDATE + ), + ChangelogEntryVerboseDto( + bpn = BusinessPartnerNonVerboseValues.bpOutputRequestCleaned.siteBpn!!, + businessPartnerType = BusinessPartnerType.SITE, + timestamp = Instant.now(), + changelogType = ChangelogType.UPDATE + ) + ) + ) + // Pool APi get changelogs endpoint + poolWireMockServer.stubFor( + WireMock.post(WireMock.urlPathEqualTo(POOL_API_SEARCH_CHANGE_LOG_URL)) + .willReturn( + WireMock.okJson(objectMapper.writeValueAsString(poolChangelogEntries)) + ) + ) + } + fun readSharingStates(businessPartnerType: BusinessPartnerType?, externalIds: Collection?): Collection { @@ -489,6 +556,7 @@ class BusinessPartnerControllerIT @Autowired constructor( @Test fun `insert one business partners and finalize cleaning task without error`() { + this.mockOrchestratorApiCleaned() val outputBusinessPartners = listOf( BusinessPartnerNonVerboseValues.bpOutputRequestCleaned @@ -544,7 +612,7 @@ class BusinessPartnerControllerIT @Autowired constructor( sharingStateType = SharingStateType.Success, sharingErrorCode = null, sharingErrorMessage = null, - bpn = BusinessPartnerGenericMockValues.businessPartner1.addressBpn, + bpn = BusinessPartnerGenericValues.businessPartner1.addressBpn, sharingProcessStarted = null, taskId = "0" ), @@ -574,7 +642,7 @@ class BusinessPartnerControllerIT @Autowired constructor( @Test fun `insert one business partners but task is missing in orchestrator`() { - + this.mockOrchestratorApiCleaned() val upsertRequests = listOf( BusinessPartnerNonVerboseValues.bpInputRequestCleaned, BusinessPartnerNonVerboseValues.bpInputRequestError, @@ -629,4 +697,49 @@ class BusinessPartnerControllerIT @Autowired constructor( } + @Test + fun `insert one business partners and request cleaning task for bpn update`() { + + this.mockPoolApiGetChangeLogs() + this.mockOrchestratorApiCleanedResponseSizeOne() + + val outputBusinessPartners = listOf( + BusinessPartnerNonVerboseValues.bpOutputRequestCleaned + ) + val upsertRequests = listOf( + BusinessPartnerNonVerboseValues.bpInputRequestCleaned + ) + gateClient.businessParters.upsertBusinessPartnersInput(upsertRequests).body!! + + val externalId4 = BusinessPartnerNonVerboseValues.bpInputRequestCleaned.externalId + + businessPartnerService.finishCleaningTask() + + businessPartnerService.requestCleaningTaskForGateOutputIfPoolBpnHasChanges() + + val upsertSharingStatesRequests = listOf( + SharingStateDto( + businessPartnerType = BusinessPartnerType.GENERIC, + externalId = externalId4, + sharingStateType = SharingStateType.Pending, + sharingErrorCode = null, + sharingErrorMessage = null, + bpn = "000000123CCC333", + sharingProcessStarted = null, + taskId = "0" + ) + ) + val externalIds = listOf(externalId4) + val upsertSharingStateResponses = readSharingStates(BusinessPartnerType.GENERIC, externalIds) + + //Assert that Cleaned Golden Record is persisted in the Output correctly + val searchResponsePage = gateClient.businessParters.getBusinessPartnersOutput(listOf(externalId4)) + assertUpsertOutputResponsesMatchRequests(searchResponsePage.content, outputBusinessPartners) + + //Assert that sharing state are created + testHelpers.assertRecursively(upsertSharingStateResponses).ignoringFieldsMatchingRegexes(".*${SharingStateDto::sharingProcessStarted.name}") + .isEqualTo(upsertSharingStatesRequests) + + + } } diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerGenericValues.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerGenericValues.kt index 284f4f783..cd0298104 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerGenericValues.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerGenericValues.kt @@ -29,7 +29,7 @@ import org.eclipse.tractusx.bpdm.common.model.DeliveryServiceType import org.eclipse.tractusx.orchestrator.api.model.* import java.time.LocalDateTime -object BusinessPartnerGenericMockValues { +object BusinessPartnerGenericValues { //Business Partner with two entries in every collection val businessPartner1 = BusinessPartnerGenericDto(