Skip to content

Commit

Permalink
Merge pull request #526 from catenax-ng/feat/database-fuzzy-search
Browse files Browse the repository at this point in the history
Pool: Replace opensearch with database fuzzysearch on Business Partners
  • Loading branch information
nicoprow authored Oct 23, 2023
2 parents 00c58d9 + 125c5ba commit 4e9bf1c
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.eclipse.tractusx.bpdm.pool.api.model.request.BusinessPartnerSearchReq
import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.SiteMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.component.opensearch.SearchService
import org.eclipse.tractusx.bpdm.pool.service.SearchService
import org.eclipse.tractusx.bpdm.pool.component.opensearch.impl.repository.AddressDocSearchRepository
import org.eclipse.tractusx.bpdm.pool.component.opensearch.impl.repository.LegalEntityDocSearchRepository
import org.eclipse.tractusx.bpdm.pool.config.OpenSearchConfigProperties
Expand All @@ -39,16 +39,15 @@ import org.eclipse.tractusx.bpdm.pool.repository.LegalEntityRepository
import org.eclipse.tractusx.bpdm.pool.repository.LogisticAddressRepository
import org.eclipse.tractusx.bpdm.pool.repository.SiteRepository
import org.eclipse.tractusx.bpdm.pool.service.*
import org.springframework.context.annotation.Primary
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import kotlin.math.ceil

/**
* Implements search functionality by using OpenSearch
*/
@Service
@Primary
class SearchServiceImpl(
val legalEntityDocSearchRepository: LegalEntityDocSearchRepository,
val addressDocSearchRepository: AddressDocSearchRepository,
Expand All @@ -71,6 +70,7 @@ class SearchServiceImpl(
* adapted accordingly from the OpenSearch page information
*
*/
@Transactional
override fun searchLegalEntities(
searchRequest: BusinessPartnerSearchRequest,
paginationRequest: PaginationRequest
Expand All @@ -88,6 +88,7 @@ class SearchServiceImpl(
/**
* @see SearchServiceImpl.searchLegalEntities
*/
@Transactional
override fun searchAddresses(searchRequest: AddressPartnerSearchRequest, paginationRequest: PaginationRequest): PageDto<AddressMatchVerboseDto> {
val addressPage = searchAndPreparePage(searchRequest, paginationRequest)

Expand All @@ -99,6 +100,7 @@ class SearchServiceImpl(
}
}

@Transactional
override fun searchSites(paginationRequest: PaginationRequest): PageDto<SiteMatchVerboseDto> {
val sitePage = searchAndPreparePageSite(paginationRequest)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import org.eclipse.tractusx.bpdm.pool.api.model.request.AddressPartnerUpdateRequ
import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressPartnerCreateResponseWrapper
import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressPartnerUpdateResponseWrapper
import org.eclipse.tractusx.bpdm.pool.component.opensearch.SearchService
import org.eclipse.tractusx.bpdm.pool.service.SearchService
import org.eclipse.tractusx.bpdm.pool.service.AddressService
import org.eclipse.tractusx.bpdm.pool.service.BusinessPartnerBuildService
import org.springframework.security.access.prepost.PreAuthorize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,11 @@ import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalAddressVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityPartnerCreateResponseWrapper
import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityPartnerUpdateResponseWrapper
import org.eclipse.tractusx.bpdm.pool.component.opensearch.SearchService
import org.eclipse.tractusx.bpdm.pool.service.SearchService
import org.eclipse.tractusx.bpdm.pool.config.BpnConfigProperties
import org.eclipse.tractusx.bpdm.pool.config.ControllerConfigProperties
import org.eclipse.tractusx.bpdm.pool.config.PoolSecurityConfigProperties
import org.eclipse.tractusx.bpdm.pool.service.AddressService
import org.eclipse.tractusx.bpdm.pool.service.BusinessPartnerBuildService
import org.eclipse.tractusx.bpdm.pool.service.BusinessPartnerFetchService
import org.eclipse.tractusx.bpdm.pool.service.SiteService
import org.eclipse.tractusx.bpdm.pool.service.*
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import org.eclipse.tractusx.bpdm.common.dto.response.PageDto
import org.eclipse.tractusx.bpdm.pool.api.PoolSiteApi
import org.eclipse.tractusx.bpdm.pool.api.model.request.*
import org.eclipse.tractusx.bpdm.pool.api.model.response.*
import org.eclipse.tractusx.bpdm.pool.component.opensearch.SearchService
import org.eclipse.tractusx.bpdm.pool.service.SearchService
import org.eclipse.tractusx.bpdm.pool.service.AddressService
import org.eclipse.tractusx.bpdm.pool.service.BusinessPartnerBuildService
import org.eclipse.tractusx.bpdm.pool.service.SiteService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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.data.repository.query.Param
import java.time.Instant

interface LegalEntityRepository : PagingAndSortingRepository<LegalEntity, Long>, CrudRepository<LegalEntity, Long> {
Expand All @@ -37,6 +38,9 @@ interface LegalEntityRepository : PagingAndSortingRepository<LegalEntity, Long>,

fun findByUpdatedAtAfter(updatedAt: Instant, pageable: Pageable): Page<LegalEntity>

@Query("SELECT p FROM LegalEntity p WHERE LOWER(p.legalName.value) LIKE :value ORDER BY LENGTH(p.legalName.value)")
fun findByLegalNameValue(value: String, pageable: Pageable): Page<LegalEntity>

@Query("SELECT DISTINCT i.legalEntity FROM LegalEntityIdentifier i WHERE i.type = :type AND upper(i.value) = upper(:idValue)")
fun findByIdentifierTypeAndValueIgnoreCase(type: IdentifierType, idValue: String): LegalEntity?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ interface LogisticAddressRepository : PagingAndSortingRepository<LogisticAddress
pageable: Pageable
): Page<LogisticAddress>

@Query("SELECT a FROM LogisticAddress a WHERE LOWER(a.name) LIKE :addressName ORDER BY LENGTH(a.name)")
fun findByName(addressName: String, pageable: Pageable): Page<LogisticAddress>

@Query("SELECT DISTINCT a FROM LogisticAddress a LEFT JOIN FETCH a.legalEntity LEFT JOIN FETCH a.legalEntity.legalAddress WHERE a IN :addresses")
fun joinLegalEntities(addresses: Set<LogisticAddress>): Set<LogisticAddress>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*******************************************************************************
* 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.pool.service

import mu.KotlinLogging
import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest
import org.eclipse.tractusx.bpdm.common.dto.response.PageDto
import org.eclipse.tractusx.bpdm.pool.api.model.request.AddressPartnerSearchRequest
import org.eclipse.tractusx.bpdm.pool.api.model.request.BusinessPartnerSearchRequest
import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.api.model.response.SiteMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.entity.LegalEntity
import org.eclipse.tractusx.bpdm.pool.entity.LogisticAddress
import org.eclipse.tractusx.bpdm.pool.entity.Site
import org.eclipse.tractusx.bpdm.pool.repository.LegalEntityRepository
import org.eclipse.tractusx.bpdm.pool.repository.LogisticAddressRepository
import org.eclipse.tractusx.bpdm.pool.repository.SiteRepository
import org.springframework.context.annotation.Primary
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service

/**
* Provides search functionality on the Catena-x database for the BPDM system
*/
@Service
@Primary
class BusinessPartnerSearchService(
private val legalEntityRepository: LegalEntityRepository,
private val businessPartnerFetchService: BusinessPartnerFetchService,
val addressService: AddressService,
val siteService: SiteService,
val logisticAddressRepository: LogisticAddressRepository,
val siteRepository: SiteRepository,
): SearchService {

private val logger = KotlinLogging.logger { }

/**
* Uses the [searchRequest] and [paginationRequest] to perform query on database for business partner.
* The BPNs of found partners are used to query the whole business partner records from the database.
* The records are supplied with relevancy scores of the search hits and returned as a paginated result.
* In case BPNs, can not be found in the database, the [PageDto] properties are
* adapted accordingly from the page information
*
*/
override fun searchLegalEntities(
searchRequest: BusinessPartnerSearchRequest,
paginationRequest: PaginationRequest
): PageDto<LegalEntityMatchVerboseDto> {
val legalEntityPage = searchAndPrepareLegalEntityPage(searchRequest, paginationRequest)
businessPartnerFetchService.fetchLegalEntityDependencies(legalEntityPage.content.map { (_, legalEntity) -> legalEntity }.toSet())

return with(legalEntityPage) {
PageDto(totalElements, totalPages, page, contentSize,
content.map { (score, legalEntity) -> legalEntity.toMatchDto(score) })
}
}

private fun searchAndPrepareLegalEntityPage(
searchRequest: BusinessPartnerSearchRequest,
paginationRequest: PaginationRequest
): PageDto<Pair<Float, LegalEntity>> {
return if (searchRequest == BusinessPartnerSearchRequest.EmptySearchRequest) {
paginateLegalEntities(paginationRequest)
} else {
searchLegalEntity(searchRequest, paginationRequest)
}
}

private fun paginateLegalEntities(paginationRequest: PaginationRequest): PageDto<Pair<Float, LegalEntity>> {
logger.debug { "Paginate database for legal entities" }
val legalEntityPage = legalEntityRepository.findAll(PageRequest.of(paginationRequest.page, paginationRequest.size))

return legalEntityPage.toDto(legalEntityPage.content.map { Pair(0f, it) }) //assigned 0 score as no ordering performed
}

private fun searchLegalEntity(
searchRequest: BusinessPartnerSearchRequest,
paginationRequest: PaginationRequest
): PageDto<Pair<Float, LegalEntity>> {
logger.debug { "Search for legal entities" }

// Concatenate and convert to lowercase.
val value = "%${searchRequest.partnerProperties.legalName}%".lowercase()
val legalEntityPage = legalEntityRepository.findByLegalNameValue(value, PageRequest.of(paginationRequest.page, paginationRequest.size))
return PageDto(
totalElements = legalEntityPage.totalElements,
totalPages = legalEntityPage.totalPages,
page = paginationRequest.page,
content = legalEntityPage.content.mapIndexed { index, legalEntity ->
val score = legalEntityPage.totalElements - paginationRequest.page * paginationRequest.size - index
Pair(score.toFloat(), legalEntity)
},
contentSize = legalEntityPage.content.size
)

}

/**
* @see searchLegalEntities
*
*/
override fun searchAddresses(
searchRequest: AddressPartnerSearchRequest,
paginationRequest: PaginationRequest
): PageDto<AddressMatchVerboseDto> {
val addressPage = searchAndPrepareAddressPage(searchRequest, paginationRequest)
addressService.fetchLogisticAddressDependencies(addressPage.content.map { (_, address) -> address }.toSet())
return with(addressPage) {
PageDto(totalElements, totalPages, page, contentSize,
content.map { (score, address) -> address.toMatchDto(score) })
}
}

private fun searchAndPrepareAddressPage(
searchRequest: AddressPartnerSearchRequest,
paginationRequest: PaginationRequest
): PageDto<Pair<Float, LogisticAddress>> {

return if (searchRequest == AddressPartnerSearchRequest.EmptySearchRequest) {
paginateAddressPartner(paginationRequest)
} else {
searchAddress(searchRequest, paginationRequest)
}
}

private fun paginateAddressPartner(paginationRequest: PaginationRequest): PageDto<Pair<Float, LogisticAddress>> {
logger.debug { "Paginate database for address partners" }
val addressPage = logisticAddressRepository.findAll(PageRequest.of(paginationRequest.page, paginationRequest.size))

return addressPage.toDto(addressPage.content.map { Pair(0f, it) }) //assigned 0 score as no ordering performed
}

private fun searchAddress(
searchRequest: AddressPartnerSearchRequest,
paginationRequest: PaginationRequest
): PageDto<Pair<Float, LogisticAddress>> {
logger.debug { "Search for addresses" }

// Concatenate and convert to lowercase.
val addressName = "%${searchRequest.name}%".lowercase()
val addressPage = logisticAddressRepository.findByName(addressName, PageRequest.of(paginationRequest.page, paginationRequest.size))
return PageDto(
totalElements = addressPage.totalElements,
totalPages = addressPage.totalPages,
page = paginationRequest.page,
content = addressPage.content.mapIndexed { index, address ->
val score = addressPage.totalElements - paginationRequest.page * paginationRequest.size - index
Pair(score.toFloat(), address)
},
contentSize = addressPage.content.size
)

}

/**
* @see searchLegalEntities
*
*/
override fun searchSites(
paginationRequest: PaginationRequest
): PageDto<SiteMatchVerboseDto> {
val sitePage = searchAndPreparePageSite(paginationRequest)
siteService.fetchSiteDependenciesPage(sitePage.content.map { site -> site }.toSet())

return with(sitePage) {
PageDto(totalElements, totalPages, page, contentSize,
content.map { site -> site.toMatchDto() })
}
}

private fun searchAndPreparePageSite(
paginationRequest: PaginationRequest
): PageDto<Site> {
return paginateSite(paginationRequest)
}

private fun paginateSite(paginationRequest: PaginationRequest): PageDto<Site> {
logger.debug { "Paginate database for sites" }
val sitePage = siteRepository.findAll(PageRequest.of(paginationRequest.page, paginationRequest.size))

return sitePage.toDto(sitePage.content.map { it })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.bpdm.pool.component.opensearch
package org.eclipse.tractusx.bpdm.pool.service

import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest
import org.eclipse.tractusx.bpdm.common.dto.response.PageDto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest
import org.eclipse.tractusx.bpdm.common.dto.response.PageDto
import org.eclipse.tractusx.bpdm.pool.Application
import org.eclipse.tractusx.bpdm.pool.api.client.PoolClientImpl
import org.eclipse.tractusx.bpdm.pool.api.model.request.BusinessPartnerSearchRequest
import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest
import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchVerboseDto
import org.eclipse.tractusx.bpdm.pool.component.opensearch.impl.service.OpenSearchSyncStarterService
import org.eclipse.tractusx.bpdm.pool.component.opensearch.impl.service.SearchServiceImpl
import org.eclipse.tractusx.bpdm.pool.util.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand All @@ -52,7 +54,8 @@ class OpenSearchControllerIT @Autowired constructor(
private val openSearchSyncService: OpenSearchSyncStarterService,
private val objectMapper: ObjectMapper,
private val testHelpers: TestHelpers,
private val poolClient: PoolClientImpl
private val poolClient: PoolClientImpl,
private val searchService: SearchServiceImpl
) {


Expand Down Expand Up @@ -176,8 +179,8 @@ class OpenSearchControllerIT @Autowired constructor(

private fun searchBusinessPartnerByName(name: String): PageDto<LegalEntityMatchVerboseDto> {

return poolClient.legalEntities.getLegalEntities(
LegalEntityPropertiesSearchRequest(name),
return searchService.searchLegalEntities(
BusinessPartnerSearchRequest(LegalEntityPropertiesSearchRequest(name)),
PaginationRequest()
)
}
Expand Down

0 comments on commit 4e9bf1c

Please sign in to comment.