Skip to content

Commit

Permalink
Merge pull request #1190 from eclipse-tractusx/feat/gate-gr-sync-check
Browse files Browse the repository at this point in the history
feat(Gate): Consistency Check with the Pool
  • Loading branch information
SujitMBRDI authored Jan 28, 2025
2 parents f783260 + 2aafb06 commit 0331d1e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C
- BPDM Gate: Add endpoints for managing business partner relations ([#1027](https://github.com/eclipse-tractusx/bpdm/issues/1027))
- BPDM System Test: End-to-end test CI/CD workflow setup for golden record process. ([#1155](https://github.com/eclipse-tractusx/bpdm/issues/1155))
- Apps : Enhanced dependency readiness checks with a scheduler to verify connections to required services every 30 seconds and during startup for Pool, Cleaning Service Dummy, and Gate service. ([#1161](https://github.com/eclipse-tractusx/bpdm/issues/1161))
- BPDM Gate: Add consistency check with the golden record Pool making sure that referenced BPNs are still existing ([#1130](https://github.com/eclipse-tractusx/bpdm/issues/1130))

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ data class GoldenRecordTaskConfigProperties(
val creation: CreationProperties = CreationProperties(),
val check: TaskProcessProperties = TaskProcessProperties(),
val healthCheck: TaskProcessProperties = TaskProcessProperties(),
val dependencyCheck: TaskProcessProperties = TaskProcessProperties()
val dependencyCheck: TaskProcessProperties = TaskProcessProperties(),
val consistencyCheck: TaskProcessProperties = TaskProcessProperties()
) {
data class CreationProperties(
val fromSharingMember: CreationTaskProperties = CreationTaskProperties(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.eclipse.tractusx.bpdm.gate.config

import jakarta.annotation.PostConstruct
import org.eclipse.tractusx.bpdm.gate.service.GoldenRecordConsistencyService
import org.eclipse.tractusx.bpdm.gate.service.GoldenRecordUpdateBatchService
import org.eclipse.tractusx.bpdm.gate.service.TaskCreationBatchService
import org.eclipse.tractusx.bpdm.gate.service.TaskResolutionBatchService
Expand All @@ -33,7 +34,8 @@ class GoldenRecordTaskConfiguration(
private val taskScheduler: TaskScheduler,
private val taskCreationService: TaskCreationBatchService,
private val taskResolutionService: TaskResolutionBatchService,
private val updateService: GoldenRecordUpdateBatchService
private val updateService: GoldenRecordUpdateBatchService,
private val goldenRecordConsistencyService: GoldenRecordConsistencyService
) {

@PostConstruct
Expand All @@ -57,6 +59,11 @@ class GoldenRecordTaskConfiguration(
{ taskResolutionService.healthCheck() },
configProperties.healthCheck.cron
)

taskScheduler.scheduleIfEnabled(
{ goldenRecordConsistencyService.check() },
configProperties.consistencyCheck.cron
)
}

private fun TaskScheduler.scheduleIfEnabled(task: Runnable, cronExpression: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
package org.eclipse.tractusx.bpdm.gate.repository.generic

import org.eclipse.tractusx.bpdm.common.model.StageType
import org.eclipse.tractusx.bpdm.gate.api.model.SharingStateType
import org.eclipse.tractusx.bpdm.gate.entity.SharingStateDb
import org.eclipse.tractusx.bpdm.gate.entity.generic.BusinessPartnerDb
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
import org.springframework.data.jpa.repository.Query
Expand Down Expand Up @@ -66,6 +69,7 @@ interface BusinessPartnerRepository : PagingAndSortingRepository<BusinessPartner
}

fun findBySharingStateInAndStage(sharingStates: Collection<SharingStateDb>, stage: StageType): Set<BusinessPartnerDb>
fun findByStageAndSharingStateSharingStateType(stage: StageType, sharingStateType: SharingStateType, pageable: Pageable): Page<BusinessPartnerDb>

@Query("SELECT b.stage as stage, COUNT(b.stage) as count FROM BusinessPartnerDb AS b GROUP BY b.stage")
fun countPerStages(): List<PartnersPerStageCount>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*******************************************************************************
* 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.gate.service

import jakarta.persistence.EntityManager
import mu.KotlinLogging
import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest
import org.eclipse.tractusx.bpdm.common.model.StageType
import org.eclipse.tractusx.bpdm.gate.api.exception.BusinessPartnerSharingError
import org.eclipse.tractusx.bpdm.gate.api.model.SharingStateType
import org.eclipse.tractusx.bpdm.gate.config.GoldenRecordTaskConfigProperties
import org.eclipse.tractusx.bpdm.gate.entity.generic.BusinessPartnerDb
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.AddressSearchRequest
import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntitySearchRequest
import org.eclipse.tractusx.bpdm.pool.api.model.request.SiteSearchRequest
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.support.TransactionTemplate

@Service
class GoldenRecordConsistencyService(
private val businessPartnerRepository: BusinessPartnerRepository,
private val poolApiClient: PoolApiClient,
private val sharingStateService: SharingStateService,
private val configProperties: GoldenRecordTaskConfigProperties,
private val transactionTemplate: TransactionTemplate,
private val entityManager: EntityManager,
private val sharingStateRepository: SharingStateRepository
) {

private val logger = KotlinLogging.logger { }

fun check(){
logger.info { "Start checking business partner BPNs for consistency with the golden record pool..." }

var currentPageNumber = 0
var totalCheckBusinessPartners = 0
lateinit var checkBatchResult: CheckResult

do{
checkBatchResult = transactionTemplate.execute { checkBatch(currentPageNumber, configProperties.consistencyCheck.batchSize) } ?: CheckResult(0, false)
currentPageNumber++
totalCheckBusinessPartners += checkBatchResult.checkedCount

entityManager.clear()
}while (checkBatchResult.hasMore)

logger.debug { "Finished checking $totalCheckBusinessPartners for consistency with the golden record pool." }
}


private fun checkBatch(pageNumber: Int, pageSize: Int): CheckResult{
val sharingStatePage = sharingStateRepository.findBySharingStateType(SharingStateType.Success, PageRequest.of(pageNumber, pageSize))
val sharingStates = sharingStatePage.content
val outputs = businessPartnerRepository.findBySharingStateInAndStage(sharingStates, StageType.Output)

val outputsByBpnL = outputs.filter { it.bpnL != null }.associateBy { it.bpnL }
val outputsByBpnS = outputs.filter { it.bpnS != null }.associateBy { it.bpnS }
val outputsByBpnA = outputs.filter { it.bpnA != null }.associateBy { it.bpnA }

val legalEntitiesToCheck = outputsByBpnL.keys.filterNotNull()
val sitesToCheck = outputsByBpnS.keys.filterNotNull()
val addressesToCheck = outputsByBpnA.keys.filterNotNull()

val foundLegalEntities = if(legalEntitiesToCheck.isNotEmpty()) poolApiClient.legalEntities.getLegalEntities(LegalEntitySearchRequest(bpnLs = legalEntitiesToCheck), PaginationRequest(0, legalEntitiesToCheck.size)).content else emptyList()
val foundSites = if(sitesToCheck.isNotEmpty()) poolApiClient.sites.getSites(SiteSearchRequest(siteBpns = sitesToCheck), PaginationRequest(0, sitesToCheck.size)).content else emptyList()
val foundAddresses = if(addressesToCheck.isNotEmpty()) poolApiClient.addresses.getAddresses(AddressSearchRequest(addressBpns = addressesToCheck), PaginationRequest(0, addressesToCheck.size)).content else emptyList()

val foundBpnLs = foundLegalEntities.map { it.legalEntity.bpnl }.toSet()
val foundBpnSs = foundSites.map { it.site.bpns }.toSet()
val foundBpnAs = foundAddresses.map { it.bpna }.toSet()

val missingLegalEntities = legalEntitiesToCheck.minus(foundBpnLs).toSet()
val missingSites = sitesToCheck.minus(foundBpnSs).toSet()
val missingAddresses = addressesToCheck.minus(foundBpnAs).toSet()

outputs.forEach { output ->
val missingBpnL = if(output.bpnL in missingLegalEntities) output.bpnL else null
val missingBpnS = if(output.bpnS in missingSites) output.bpnS else null
val missingBpnA = if(output.bpnA in missingAddresses) output.bpnA else null

val missingBpns = listOfNotNull(missingBpnL, missingBpnS, missingBpnA)

if(missingBpns.isNotEmpty())
setConsistencyError(output, missingBpns)
}

return CheckResult(sharingStatePage.content.size, sharingStatePage.hasNext())
}

private fun setConsistencyError(businessPartner: BusinessPartnerDb, missingBpns: List<String>){
val sharingState = businessPartner.sharingState
val errorMessage = "Business partner with external-ID ${sharingState.externalId} references not existing golden record '${missingBpns.joinToString()}'"
sharingStateService.setError(sharingState, sharingErrorCode = BusinessPartnerSharingError.BpnNotInPool, errorMessage)
}

private data class CheckResult(
val checkedCount: Int,
val hasMore: Boolean
)
}
5 changes: 5 additions & 0 deletions bpdm-gate/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ bpdm:
dependencyCheck:
# How often the golden record connection dependencies should be checked for being healthy
cron: '*/30 * * * * *'
consistencyCheck:
# How many business partners should be checked for consistency at one
batchSize: 20
# How often referenced BPNs should be checked for consistency with the golden record pool
cron: '0 0 0 * * *'

# Connection to the pool and orchestrator
client:
Expand Down

0 comments on commit 0331d1e

Please sign in to comment.