diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/impl/service/SearchServiceImpl.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/impl/service/SearchServiceImpl.kt index 7f7e6d80b..f592f7682 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/impl/service/SearchServiceImpl.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/impl/service/SearchServiceImpl.kt @@ -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 @@ -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, @@ -71,6 +70,7 @@ class SearchServiceImpl( * adapted accordingly from the OpenSearch page information * */ + @Transactional override fun searchLegalEntities( searchRequest: BusinessPartnerSearchRequest, paginationRequest: PaginationRequest @@ -88,6 +88,7 @@ class SearchServiceImpl( /** * @see SearchServiceImpl.searchLegalEntities */ + @Transactional override fun searchAddresses(searchRequest: AddressPartnerSearchRequest, paginationRequest: PaginationRequest): PageDto { val addressPage = searchAndPreparePage(searchRequest, paginationRequest) @@ -99,6 +100,7 @@ class SearchServiceImpl( } } + @Transactional override fun searchSites(paginationRequest: PaginationRequest): PageDto { val sitePage = searchAndPreparePageSite(paginationRequest) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/AddressController.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/AddressController.kt index 005e1a564..9702dfe38 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/AddressController.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/AddressController.kt @@ -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 diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/LegalEntityController.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/LegalEntityController.kt index e2beff0d1..3d3698965 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/LegalEntityController.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/LegalEntityController.kt @@ -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 diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/SiteController.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/SiteController.kt index 77fab3b20..51ecf96d3 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/SiteController.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/SiteController.kt @@ -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 diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt index 45c38ace5..7fafeef21 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt @@ -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, CrudRepository { @@ -37,6 +38,9 @@ interface LegalEntityRepository : PagingAndSortingRepository, fun findByUpdatedAtAfter(updatedAt: Instant, pageable: Pageable): Page + @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 + @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? diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt index 8279a4787..999d50711 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt @@ -43,6 +43,9 @@ interface LogisticAddressRepository : PagingAndSortingRepository + @Query("SELECT a FROM LogisticAddress a WHERE LOWER(a.name) LIKE :addressName ORDER BY LENGTH(a.name)") + fun findByName(addressName: String, pageable: Pageable): Page + @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): Set diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt new file mode 100644 index 000000000..8c3a437da --- /dev/null +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt @@ -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 { + 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> { + return if (searchRequest == BusinessPartnerSearchRequest.EmptySearchRequest) { + paginateLegalEntities(paginationRequest) + } else { + searchLegalEntity(searchRequest, paginationRequest) + } + } + + private fun paginateLegalEntities(paginationRequest: PaginationRequest): PageDto> { + 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> { + 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 { + 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> { + + return if (searchRequest == AddressPartnerSearchRequest.EmptySearchRequest) { + paginateAddressPartner(paginationRequest) + } else { + searchAddress(searchRequest, paginationRequest) + } + } + + private fun paginateAddressPartner(paginationRequest: PaginationRequest): PageDto> { + 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> { + 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 { + 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 { + return paginateSite(paginationRequest) + } + + private fun paginateSite(paginationRequest: PaginationRequest): PageDto { + logger.debug { "Paginate database for sites" } + val sitePage = siteRepository.findAll(PageRequest.of(paginationRequest.page, paginationRequest.size)) + + return sitePage.toDto(sitePage.content.map { it }) + } +} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/SearchService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt similarity index 97% rename from bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/SearchService.kt rename to bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt index 717d53521..5ae8e9246 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/SearchService.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt @@ -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 diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt index ac597bf4c..3ea7b835c 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt @@ -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 @@ -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 ) { @@ -176,8 +179,8 @@ class OpenSearchControllerIT @Autowired constructor( private fun searchBusinessPartnerByName(name: String): PageDto { - return poolClient.legalEntities.getLegalEntities( - LegalEntityPropertiesSearchRequest(name), + return searchService.searchLegalEntities( + BusinessPartnerSearchRequest(LegalEntityPropertiesSearchRequest(name)), PaginationRequest() ) }