diff --git a/REST_APIs.md b/REST_APIs.md index 2dfc1c1..426ce9e 100644 --- a/REST_APIs.md +++ b/REST_APIs.md @@ -1,6 +1,6 @@ # DOME Search REST APIs -**Version:** 1.1.4 +**Version:** 1.2.0 **Description:** DOME Search REST APIs Swagger documentation @@ -11,29 +11,32 @@ |------|------|------| | POST | `/api/RandomizedProductOfferings` | postRandomizedProductOfferings | -### search-resource +### provider-resource | Verb | Path | Task | |------|------|------| -| POST | `/api/SearchProduct/{query}` | searchProduct | -| POST | `/api/SearchProductByFilterCategory` | searchProductByFilterCategory | -| GET | `/api/offerings/clearRepository` | clearRepository | +| POST | `/api/SearchOrganizations` | searchOrganizations | +| GET | `/api/categories` | getCategories | +| GET | `/api/complianceLevels` | getComplianceLevels | +| GET | `/api/countries` | getCountries | +| GET | `/api/organizations/clearRepository` | clearRepository | -### provider-resource +### search-resource | Verb | Path | Task | |------|------|------| -| POST | `/api/providersByCategories` | getProvidersByCategories | -| POST | `/api/searchOrganizations` | searchOrganizations | +| POST | `/api/SearchProduct/{query}` | searchProduct | +| POST | `/api/SearchProductByFilterCategory` | searchProductByFilterCategory | +| GET | `/api/offerings/clearRepository` | clearRepositoryUsingGET_1 | ### basic-error-controller | Verb | Path | Task | |------|------|------| -| GET | `/error` | error | -| HEAD | `/error` | error | -| POST | `/error` | error | -| PUT | `/error` | error | -| DELETE | `/error` | error | -| OPTIONS | `/error` | error | -| PATCH | `/error` | error | +| GET | `/error` | errorHtml | +| HEAD | `/error` | errorHtml | +| POST | `/error` | errorHtml | +| PUT | `/error` | errorHtml | +| DELETE | `/error` | errorHtml | +| OPTIONS | `/error` | errorHtml | +| PATCH | `/error` | errorHtml | ### info-search-controller | Verb | Path | Task | diff --git a/pom.xml b/pom.xml index 1625ae1..983364e 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ it.eng.dome search - 1.1.4 + 1.2.0 Search and Browsing Demo for DOME Project @@ -82,7 +82,7 @@ it.eng.dome.brokerage brokerage-utils - 2.2.7 + 2.2.8 diff --git a/release_note.md b/release_note.md index 7ac1764..3acd3f1 100644 --- a/release_note.md +++ b/release_note.md @@ -2,6 +2,18 @@ **Release Notes** for the *Search*: +### 1.2.0 +**Feature** +* Introduced new Elasticsearch index `provider-index` dedicated to Organizations (providers) indexing. +* Implemented `ProviderIndexingService` to manage the indexing process of Organizations in the `provider-index` based on ProductOfferings store in ES (`Launched` status only) and considering only DOME Catalog Categories. +* Added orchestration layer to manage the indexing process of both ProductOfferings and Organizations. +* Update REST APIs to support and expose the new `provider-index` for search and retrieval of Organizations. +* Added new attribute `considerAllOrgs` in REST APIs to allow users to choose whether to consider all indexed Organizations or only those Organizations that have at least one ProductOffering on the Marketplace. + +**Improvement** +* Usage of the `Brokerage Utils 2.2.8`. +* Set `catalog url` using `DOME_BASE_URL` variable in application.yaml. + ### 1.1.4 **Bug Fix** * Set `ReadTimeout` using `timeout` variable in application.yaml. diff --git a/src/main/java/it/eng/dome/search/config/RestTemplateConfig.java b/src/main/java/it/eng/dome/search/config/RestTemplateConfig.java new file mode 100644 index 0000000..6e34225 --- /dev/null +++ b/src/main/java/it/eng/dome/search/config/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package it.eng.dome.search.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/it/eng/dome/search/domain/ProviderIndex.java b/src/main/java/it/eng/dome/search/domain/ProviderIndex.java new file mode 100644 index 0000000..45864ac --- /dev/null +++ b/src/main/java/it/eng/dome/search/domain/ProviderIndex.java @@ -0,0 +1,42 @@ +package it.eng.dome.search.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import it.eng.dome.search.domain.dto.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Document(indexName = "provider-index") +@JsonIgnoreProperties(ignoreUnknown = true) +public class ProviderIndex { + + @Id + protected String id; + + @Field(type = FieldType.Text) + private String tradingName; + + @Field(type = FieldType.Object) + private OrganizationDTO organization; + + @Field(type = FieldType.Nested) + private List categories; + + @Field(type = FieldType.Keyword) + private String country; + + @Field(type = FieldType.Keyword) + private List complianceLevels; + + @Field(type = FieldType.Integer) + private Integer publishedOfferingsCount; +} diff --git a/src/main/java/it/eng/dome/search/domain/dto/ExternalReferenceDTO.java b/src/main/java/it/eng/dome/search/domain/dto/ExternalReferenceDTO.java new file mode 100644 index 0000000..ddc37df --- /dev/null +++ b/src/main/java/it/eng/dome/search/domain/dto/ExternalReferenceDTO.java @@ -0,0 +1,13 @@ +package it.eng.dome.search.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ExternalReferenceDTO { + private String externalReferenceType; + private String name; +} diff --git a/src/main/java/it/eng/dome/search/domain/dto/OrganizationDTO.java b/src/main/java/it/eng/dome/search/domain/dto/OrganizationDTO.java new file mode 100644 index 0000000..4be5fbd --- /dev/null +++ b/src/main/java/it/eng/dome/search/domain/dto/OrganizationDTO.java @@ -0,0 +1,19 @@ +package it.eng.dome.search.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OrganizationDTO { + private String id; + private String href; + private String tradingName; + private List externalReference; + private List organizationIdentification; + private List partyCharacteristic; +} diff --git a/src/main/java/it/eng/dome/search/domain/dto/OrganizationIdentificationDTO.java b/src/main/java/it/eng/dome/search/domain/dto/OrganizationIdentificationDTO.java new file mode 100644 index 0000000..929962d --- /dev/null +++ b/src/main/java/it/eng/dome/search/domain/dto/OrganizationIdentificationDTO.java @@ -0,0 +1,22 @@ +package it.eng.dome.search.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OrganizationIdentificationDTO { + private String identificationId; + private String identificationType; + private String issuingAuthority; +// private OffsetDateTime issuingDate; +// private AttachmentRefOrValue attachment; +// private TimePeriod validFor; +// private String atBaseType; +// private URI atSchemaLocation; + @JsonProperty("@type") + private String atType; +} diff --git a/src/main/java/it/eng/dome/search/domain/dto/PartyCharacteristicDTO.java b/src/main/java/it/eng/dome/search/domain/dto/PartyCharacteristicDTO.java new file mode 100644 index 0000000..807961b --- /dev/null +++ b/src/main/java/it/eng/dome/search/domain/dto/PartyCharacteristicDTO.java @@ -0,0 +1,13 @@ +package it.eng.dome.search.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PartyCharacteristicDTO { + private String name; + private Object value; +} diff --git a/src/main/java/it/eng/dome/search/indexing/IndexingManager.java b/src/main/java/it/eng/dome/search/indexing/IndexingManager.java index 10fa350..8948b0c 100644 --- a/src/main/java/it/eng/dome/search/indexing/IndexingManager.java +++ b/src/main/java/it/eng/dome/search/indexing/IndexingManager.java @@ -1,6 +1,7 @@ package it.eng.dome.search.indexing; import it.eng.dome.search.domain.IndexingObject; +import it.eng.dome.search.domain.ProviderIndex; import it.eng.dome.search.service.TmfDataRetriever; import it.eng.dome.tmforum.tmf620.v4.model.*; import it.eng.dome.tmforum.tmf632.v4.model.Organization; @@ -114,6 +115,32 @@ private IndexingObject mapResourceSpec(ProductSpecification productSpec, Indexin return objToIndex; } + public ProviderIndex processProviderFromIndexingObject(Organization organization, List offerings, ProviderIndex objToIndex, List domeCatalogCategories) { + try { + + if (organization == null || organization.getId() == null) { + log.warn("Organization is null or has no ID. Skipping."); + return objToIndex; + } + + // ---- Map Organization basic metadata ---- + objToIndex = mappingManager.prepareOrganizationMetadata(organization, objToIndex); + + // ---- Aggregation from offerings ---- + objToIndex = mappingManager.prepareProviderAggregationMetadata(offerings, objToIndex, domeCatalogCategories); + + // ---- Published offerings count ---- + int publishedCount = (offerings != null) ? offerings.size() : 0; + objToIndex.setPublishedOfferingsCount(publishedCount); + + } catch (Exception e) { + log.warn("Exception - Error during processProviderFromTMForum(). Skipped: {}", + e.getMessage(), e); + } + + return objToIndex; + } + /* private IndexingObject processSemantic(IndexingObject objToIndex, ProductOffering product) { // Reactivate for Semantic services diff --git a/src/main/java/it/eng/dome/search/indexing/MappingManager.java b/src/main/java/it/eng/dome/search/indexing/MappingManager.java index 8242780..0b6057f 100644 --- a/src/main/java/it/eng/dome/search/indexing/MappingManager.java +++ b/src/main/java/it/eng/dome/search/indexing/MappingManager.java @@ -1,48 +1,37 @@ package it.eng.dome.search.indexing; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; - -import org.jsoup.Jsoup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.ResourceAccessException; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import it.eng.dome.search.domain.IndexingObject; -import it.eng.dome.search.domain.dto.CategoryDTO; -import it.eng.dome.search.domain.dto.ProductOfferingDTO; -import it.eng.dome.search.domain.dto.ProductOfferingPriceDTO; -import it.eng.dome.search.domain.dto.ProductSpecCharacteristicDTO; -import it.eng.dome.search.domain.dto.ProductSpecCharacteristicValueDTO; -import it.eng.dome.search.domain.dto.ProductSpecificationDTO; -import it.eng.dome.search.domain.dto.RelatedPartyDTO; -import it.eng.dome.search.domain.dto.ResourceSpecificationDTO; -import it.eng.dome.search.domain.dto.ServiceSpecificationDTO; +import it.eng.dome.search.domain.ProviderIndex; +import it.eng.dome.search.domain.dto.*; import it.eng.dome.search.rest.web.util.RestSemanticUtil; import it.eng.dome.search.semantic.domain.Analysis; import it.eng.dome.search.semantic.domain.AnalyzeResultObject; import it.eng.dome.search.semantic.domain.CategorizationResultObject; import it.eng.dome.search.service.TmfDataRetriever; import it.eng.dome.search.util.VCDecoderBasic; -import it.eng.dome.tmforum.tmf620.v4.model.CategoryRef; -import it.eng.dome.tmforum.tmf620.v4.model.CharacteristicValueSpecification; -import it.eng.dome.tmforum.tmf620.v4.model.ProductOffering; -import it.eng.dome.tmforum.tmf620.v4.model.ProductOfferingPriceRefOrValue; -import it.eng.dome.tmforum.tmf620.v4.model.ProductSpecification; -import it.eng.dome.tmforum.tmf620.v4.model.ProductSpecificationCharacteristic; -import it.eng.dome.tmforum.tmf620.v4.model.ProductSpecificationRef; -import it.eng.dome.tmforum.tmf620.v4.model.RelatedParty; -import it.eng.dome.tmforum.tmf620.v4.model.ResourceSpecificationRef; -import it.eng.dome.tmforum.tmf620.v4.model.ServiceSpecificationRef; +import it.eng.dome.tmforum.tmf620.v4.model.*; +import it.eng.dome.tmforum.tmf632.v4.model.Characteristic; +import it.eng.dome.tmforum.tmf632.v4.model.ExternalReference; +import it.eng.dome.tmforum.tmf632.v4.model.Organization; +import it.eng.dome.tmforum.tmf632.v4.model.OrganizationIdentification; import it.eng.dome.tmforum.tmf633.v4.model.ServiceSpecification; import it.eng.dome.tmforum.tmf634.v4.model.ResourceSpecification; +import org.jsoup.Jsoup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.ResourceAccessException; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Component public class MappingManager { @@ -100,29 +89,8 @@ public IndexingObject prepareProdSpecMetadata(ProductSpecification productSpecDe // } // compliance levels - List complianceLevels = new ArrayList<>(); - if (prodSpecDTO.getProductSpecCharacteristic() != null) { - for (ProductSpecCharacteristicDTO characteristic : prodSpecDTO.getProductSpecCharacteristic()) { - // decode only if the name is Compliance:VC - if ("Compliance:VC".equalsIgnoreCase(characteristic.getName())) { - if (characteristic.getProductSpecCharacteristicValue() != null) { - for (ProductSpecCharacteristicValueDTO charValueDTO : characteristic.getProductSpecCharacteristicValue()) { - try { - String complianceLevel = VCDecoderBasic.extractLabelLevel(charValueDTO.getValue()); - if (complianceLevel != null && !complianceLevel.isBlank()) { - log.info("Decoded VC for ProductSpecCharacteristicValue {}: {}", charValueDTO.getValue(), complianceLevel); - complianceLevels.add(complianceLevel); - } - } catch (Exception e) { - log.warn("Failed to decode VC for ProductSpecCharacteristicValue {}: {}", charValueDTO.getValue(), e.getMessage()); - } - } - } - } - } - } - objToIndex.setComplianceLevels(complianceLevels); - + // invece di tutta la logica presente prima + objToIndex.setComplianceLevels(extractComplianceLevels(productSpecDetails)); return objToIndex; } @@ -458,4 +426,162 @@ private CategoryDTO toCategoryDTO(CategoryRef categoryRef) { return dto; } -} + private OrganizationDTO toOrganizationDTO(Organization organization) { + OrganizationDTO dto = new OrganizationDTO(); + dto.setId(organization.getId()); + dto.setHref(organization.getHref()); + dto.setTradingName(organization.getTradingName()); + dto.setExternalReference(toExternalReferenceDTOList(organization.getExternalReference())); + dto.setOrganizationIdentification(toOrganizationIdentificationDTOList(organization.getOrganizationIdentification())); + dto.setPartyCharacteristic(toPartyCharacteristicDTOList(organization.getPartyCharacteristic())); + return dto; + } + + private List toExternalReferenceDTOList(List list) { + List dtos = new ArrayList<>(); + if(list!=null) { + for (ExternalReference r : list) { + ExternalReferenceDTO dto = new ExternalReferenceDTO(); + dto.setExternalReferenceType(r.getExternalReferenceType() != null ? r.getExternalReferenceType() : null); + dto.setName(r.getName() != null ? r.getName() : null); + dtos.add(dto); + } + } + return dtos; + } + + private List toOrganizationIdentificationDTOList(List list) { + List dtos = new ArrayList<>(); + if(list!=null) { + for (OrganizationIdentification o : list) { + OrganizationIdentificationDTO dto = new OrganizationIdentificationDTO(); + dto.setIdentificationId(o.getIdentificationId() != null ? o.getIdentificationId() : null); + dto.setIdentificationType(o.getIdentificationType() != null ? o.getIdentificationType() : null); + dto.setIssuingAuthority(o.getIssuingAuthority() != null ? o.getIssuingAuthority() : null); + dto.setAtType(o.getAtType() != null ? o.getAtType() : null); + dtos.add(dto); + } + } + return dtos; + } + + public List toPartyCharacteristicDTOList(List list) { + List dtos = new ArrayList<>(); + if(list!=null) { + for (Characteristic c : list) { + PartyCharacteristicDTO dto = new PartyCharacteristicDTO(); + dto.setName(c.getName() != null ? c.getName() : null); + dto.setValue(c.getValue() != null ? c.getValue().toString() : null); + dtos.add(dto); + } + } + return dtos; + } + + private String extractCountry(Organization organization) { + if (organization.getPartyCharacteristic() == null) { + return null; + } + + for (Characteristic c : organization.getPartyCharacteristic()) { + if ("country".equalsIgnoreCase(c.getName()) && c.getValue() != null) { + return c.getValue().toString(); + } + } + + return null; + } + + // --- helper to extract compliance levels --- + private List extractComplianceLevels(ProductSpecification spec) { + List complianceLevels = new ArrayList<>(); + + if (spec != null && spec.getProductSpecCharacteristic() != null) { + for (ProductSpecificationCharacteristic characteristic : spec.getProductSpecCharacteristic()) { + if ("Compliance:VC".equalsIgnoreCase(characteristic.getName()) + && characteristic.getProductSpecCharacteristicValue() != null) { + + for (CharacteristicValueSpecification value : characteristic.getProductSpecCharacteristicValue()) { + try { + String complianceLevel = VCDecoderBasic.extractLabelLevel(value.getValue().toString()); + if (complianceLevel != null && !complianceLevel.isBlank()) { + log.info("Decoded VC for ProductSpecCharacteristicValue {}: {}", value.getValue(), complianceLevel); + complianceLevels.add(complianceLevel); + } + } catch (Exception e) { + log.warn("Failed to decode VC for ProductSpecCharacteristicValue {}: {}", value.getValue(), e.getMessage()); + } + } + } + } + } + + return complianceLevels.stream().distinct().collect(Collectors.toList()); + } + + // PROVIDER SPECIFIC MAPPING + public ProviderIndex prepareOrganizationMetadata(Organization organization, ProviderIndex objToIndex) { + OrganizationDTO organizationDTO = toOrganizationDTO(organization); + objToIndex.setOrganization(organizationDTO); + + objToIndex.setId(organization.getId()); + + // fallback: tradingName -> name + objToIndex.setTradingName( + organization.getTradingName() != null + ? organization.getTradingName() + : organization.getName() + ); + + objToIndex.setCountry(extractCountry(organization)); + + return objToIndex; + } + + public ProviderIndex prepareProviderAggregationMetadata( + List offerings, + ProviderIndex objToIndex, + List domeCatalogCategories) { + + Set categories = new HashSet<>(); + Set complianceLevels = new HashSet<>(); + + if (offerings != null) { + + for (IndexingObject offering : offerings) { + + // ---- Categories ---- + if (offering.getCategories() != null) { + + for (CategoryDTO category : offering.getCategories()) { + + if (category == null || category.getName() == null) { + continue; + } + + // filter by DOME catalog categories if provided + if (domeCatalogCategories == null + || domeCatalogCategories.contains(category.getName())) { + + categories.add(category); + + } else { + log.debug("Category {} ignored because not in DOME Catalog Categories", + category.getName()); + } + } + } + + // ---- Compliance Levels ---- + if (offering.getComplianceLevels() != null) { + complianceLevels.addAll(offering.getComplianceLevels()); + } + } + } + + objToIndex.setCategories(new ArrayList<>(categories)); + objToIndex.setComplianceLevels(new ArrayList<>(complianceLevels)); + + return objToIndex; + } +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/repository/ProviderIndexRepository.java b/src/main/java/it/eng/dome/search/repository/ProviderIndexRepository.java new file mode 100644 index 0000000..5dcfbc8 --- /dev/null +++ b/src/main/java/it/eng/dome/search/repository/ProviderIndexRepository.java @@ -0,0 +1,16 @@ +package it.eng.dome.search.repository; + +import it.eng.dome.search.domain.ProviderIndex; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ProviderIndexRepository extends ElasticsearchRepository { + // qui rimangono i metodi derivati standard + List findByCountry(String country); + List findByComplianceLevelsIn(List complianceLevels); + List findByTradingNameContainingIgnoreCase(String tradingName); + List findByCountryAndComplianceLevelsIn(String country, List complianceLevels); +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/DomeCatalogService.java b/src/main/java/it/eng/dome/search/service/DomeCatalogService.java new file mode 100644 index 0000000..feb4536 --- /dev/null +++ b/src/main/java/it/eng/dome/search/service/DomeCatalogService.java @@ -0,0 +1,83 @@ +package it.eng.dome.search.service; + +import it.eng.dome.search.service.dto.CatalogResponse; +import it.eng.dome.search.service.dto.DomeConfigResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class DomeCatalogService { + + private final RestTemplate restTemplate; + + @Value("${dome.catalog.base-url}") + private String baseUrl; + + @Value("${dome.catalog.catalog-path}") + private String catalogPath; + + @Value("${dome.catalog.config-path}") + private String configPath; + + public DomeCatalogService(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + /** + * retrieve defaultId from config endpoint DOME + */ + public String getCatalogSource() { + String url = buildUrl(configPath); + try { + DomeConfigResponse response = restTemplate.getForObject(url, DomeConfigResponse.class); + + if (response == null || response.getDefaultId() == null) { + throw new IllegalStateException("defaultId not found in config DOME"); + } + + return response.getDefaultId(); + } catch (RestClientException e) { + throw new IllegalStateException("Error calling config DOME: " + e.getMessage(), e); + } + } + + /** + * retrieve categories from catalog endpoint DOME using defaultId from config endpoint + */ + public List getCatalogCategories() { + String defaultId = getCatalogSource(); + String url = buildUrl(catalogPath, defaultId); + + try { + CatalogResponse response = restTemplate.getForObject(url, CatalogResponse.class); + + if (response == null || response.getCategory() == null) { + throw new IllegalStateException("Categories not found for catalogId: " + defaultId); + } + + return response.getCategory() + .stream() + .map(CatalogResponse.Category::getName) + .collect(Collectors.toList()); + + } catch (RestClientException e) { + throw new IllegalStateException("Error calling catalog DOME: " + e.getMessage(), e); + } + } + + /** + * Build full URL combining baseUrl + path segments + */ + private String buildUrl(String... paths) { + StringBuilder sb = new StringBuilder(baseUrl.replaceAll("/$", "")); + for (String path : paths) { + sb.append(path.startsWith("/") ? path : "/" + path); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/IndexingOrchestrator.java b/src/main/java/it/eng/dome/search/service/IndexingOrchestrator.java new file mode 100644 index 0000000..8d9df8b --- /dev/null +++ b/src/main/java/it/eng/dome/search/service/IndexingOrchestrator.java @@ -0,0 +1,60 @@ +package it.eng.dome.search.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +public class IndexingOrchestrator { + + private static final Logger log = + LoggerFactory.getLogger(IndexingOrchestrator.class); + + private final IndexingService indexingService; + private final ProviderIndexingService providerIndexingService; + + private final AtomicBoolean running = new AtomicBoolean(false); + + public IndexingOrchestrator(IndexingService indexingService, + ProviderIndexingService providerIndexingService) { + this.indexingService = indexingService; + this.providerIndexingService = providerIndexingService; + } + + // every 5 minutes + @Scheduled(cron = "0 */5 * * * ?") + public void runFullIndexingFlow() { + + if (!running.compareAndSet(false, true)) { + log.warn("Full indexing flow already running, skipping."); + return; + } + + long start = System.currentTimeMillis(); + + try { + log.info("========== FULL INDEXING FLOW STARTED =========="); + + // STEP 1 → offerings + log.info("Step 1: ProductOffering indexing started"); + indexingService.indexing(); + log.info("Step 1 completed"); + + // STEP 2 → providers + log.info("Step 2: Provider indexing started"); + providerIndexingService.indexing(); + log.info("Step 2 completed"); + + long duration = System.currentTimeMillis() - start; + log.info("========== FULL INDEXING FLOW COMPLETED in {} ms ==========", duration); + + } catch (Exception e) { + log.error("Error during full indexing flow", e); + } finally { + running.set(false); + } + } +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/IndexingService.java b/src/main/java/it/eng/dome/search/service/IndexingService.java index dd9e7d0..c4257d9 100644 --- a/src/main/java/it/eng/dome/search/service/IndexingService.java +++ b/src/main/java/it/eng/dome/search/service/IndexingService.java @@ -7,7 +7,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.List; @@ -29,7 +28,7 @@ public class IndexingService { private final AtomicBoolean running = new AtomicBoolean(false); - @Scheduled(fixedDelay = 300000) // every 5 minutes +// @Scheduled(fixedDelay = 300000) // every 5 minutes public void indexing() { // Avoid overlapping executions if (!running.compareAndSet(false, true)) { diff --git a/src/main/java/it/eng/dome/search/service/ProviderIndexingService.java b/src/main/java/it/eng/dome/search/service/ProviderIndexingService.java new file mode 100644 index 0000000..de9f421 --- /dev/null +++ b/src/main/java/it/eng/dome/search/service/ProviderIndexingService.java @@ -0,0 +1,164 @@ +package it.eng.dome.search.service; + +import it.eng.dome.search.domain.IndexingObject; +import it.eng.dome.search.domain.ProviderIndex; +import it.eng.dome.search.domain.dto.RelatedPartyDTO; +import it.eng.dome.search.indexing.IndexingManager; +import it.eng.dome.search.repository.OfferingRepository; +import it.eng.dome.search.repository.ProviderIndexRepository; +import it.eng.dome.tmforum.tmf632.v4.model.Organization; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +public class ProviderIndexingService { + + private static final Logger log = LoggerFactory.getLogger(ProviderIndexingService.class); + + @Autowired + private TmfDataRetriever tmfDataRetriever; + + @Autowired + private ProviderIndexRepository providerIndexRepository; + + @Autowired + private IndexingManager indexingManager; + + private final DomeCatalogService domeCatalogService; + + private final OfferingRepository offeringRepository; + + private final AtomicBoolean running = new AtomicBoolean(false); + + public ProviderIndexingService (DomeCatalogService domeCatalogService, OfferingRepository offeringRepository) { + this.domeCatalogService = domeCatalogService; + this.offeringRepository = offeringRepository; + } + +// @Scheduled(fixedDelay = 100000) // each 100 seconds + public void indexing() { + + if (!running.compareAndSet(false, true)) { + log.warn("ProviderIndexing already running, skipping execution."); + return; + } + try { + log.info("Starting Provider indexing process... (using ElasticSearch)"); + + /* + * STEP 1 & 2 + * Retrieve offerings paginated and build map: organizationId -> List (Launched only) + */ + Map> organizationToOfferings = new HashMap<>(); + + Pageable pageable = PageRequest.of(0, 500); + Page page; + + do { + page = offeringRepository.findAll(pageable); + + for (IndexingObject io : page.getContent()) { + + if (!"Launched".equalsIgnoreCase(io.getProductOfferingLifecycleStatus())) { + continue; + } + + if (io.getRelatedParties() == null) { + continue; + } + + for (RelatedPartyDTO rp : io.getRelatedParties()) { + + if ("Seller".equalsIgnoreCase(rp.getRole()) && rp.getId() != null) { + organizationToOfferings + .computeIfAbsent(rp.getId(), k -> new ArrayList<>()) + .add(io); + } + } + } + + pageable = page.nextPageable(); + + } while (page.hasNext()); + + log.info("Found {} organizations with launched offerings", organizationToOfferings.size()); + + /* + * STEP 3 + * Retrieve all Organizations from TMF + */ + List allOrganizations = + tmfDataRetriever.getAllPaginatedOrganizations(null, null, 50); + + log.info("Found {} total organizations", + allOrganizations.size()); + + /* + * STEP 4 + * Retrieve DOME catalog categories + */ + List domeCatalogCategories = domeCatalogService.getCatalogCategories(); + log.info("Found {} categories in DOME Catalog", + domeCatalogCategories.size()); + + /* + * STEP 5 + * Build ProviderIndex list + */ + List providersToSave = new ArrayList<>(); + + for (Organization organization : allOrganizations) { + + if (organization == null || organization.getId() == null) { + continue; + } + + String orgId = organization.getId(); + + List offerings = + organizationToOfferings.getOrDefault(orgId, Collections.emptyList()); + + ProviderIndex existing = + providerIndexRepository.findById(orgId) + .orElse(new ProviderIndex()); + + ProviderIndex processed = + indexingManager.processProviderFromIndexingObject( + organization, + offerings, + existing, + domeCatalogCategories + ); + + providersToSave.add(processed); + } + + /* + * STEP 6 + * Bulk save + */ + providerIndexRepository.saveAll(providersToSave); + + log.info("Provider indexing completed successfully. {} providers indexed.", + providersToSave.size()); + + } catch (Exception e) { + log.error("Unexpected error during Provider indexing: {}", + e.getMessage(), e); + } finally { + running.set(false); + } + } + + public void clearRepository() { + providerIndexRepository.deleteAll(); + } +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/ProviderProcessor.java b/src/main/java/it/eng/dome/search/service/ProviderProcessor.java new file mode 100644 index 0000000..0a927a8 --- /dev/null +++ b/src/main/java/it/eng/dome/search/service/ProviderProcessor.java @@ -0,0 +1,146 @@ +package it.eng.dome.search.service; + +import it.eng.dome.search.domain.ProviderIndex; +import it.eng.dome.search.service.dto.OrganizationSearchRequest; +import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.bucket.nested.Nested; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class ProviderProcessor { + + @Autowired + private ElasticsearchOperations elasticsearchOperations; + + public ProviderProcessor(ElasticsearchOperations elasticsearchOperations) { + this.elasticsearchOperations = elasticsearchOperations; + } + + // --- search provider con considerAllOrgs --- + public Page searchProvider(OrganizationSearchRequest request, boolean considerAllOrgs, Pageable pageable) { + try { + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + + // Filter by categories (nested) + if (request.getCategories() != null && !request.getCategories().isEmpty()) { + for (String cat : request.getCategories()) { + boolQuery.should(QueryBuilders.nestedQuery( + "categories", + QueryBuilders.termQuery("categories.name", cat), + ScoreMode.None + )); + } + boolQuery.minimumShouldMatch(1); + } + + // Filter by countries + if (request.getCountries() != null && !request.getCountries().isEmpty()) { + boolQuery.filter(QueryBuilders.termsQuery("country", request.getCountries())); + } + + // Filter by complianceLevels + if (request.getComplianceLevels() != null && !request.getComplianceLevels().isEmpty()) { + boolQuery.filter(QueryBuilders.termsQuery("complianceLevels", request.getComplianceLevels())); + } + + // --- filtro per organizzazioni con offering solo se considerAllOrgs = false --- + if (!considerAllOrgs) { + boolQuery.filter(QueryBuilders.rangeQuery("publishedOfferingsCount").gte(1)); + } + + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder() + .withQuery(boolQuery) + .withPageable(pageable); + + SearchHits searchHits = elasticsearchOperations.search(queryBuilder.build(), ProviderIndex.class); + + List results = searchHits.stream() + .map(hit -> hit.getContent()) + .collect(Collectors.toList()); + + return new PageImpl<>(results, pageable, searchHits.getTotalHits()); + + } catch (Exception e) { + e.printStackTrace(); + return new PageImpl<>(new ArrayList<>()); + } + } + + // --- Aggregazioni con considerAllOrgs --- + public List getAllCountries(boolean considerAllOrgs) { + return getAggregatedField("country", considerAllOrgs); + } + + public List getAllComplianceLevels(boolean considerAllOrgs) { + return getAggregatedField("complianceLevels", considerAllOrgs); + } + + public List getAllCategories(boolean considerAllOrgs) { + // Nested aggregation + NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder().withMaxResults(0); + + if (!considerAllOrgs) { + builder.withQuery(QueryBuilders.rangeQuery("publishedOfferingsCount").gte(1)); + } + + builder.withAggregations( + AggregationBuilders.nested("nested_categories", "categories") + .subAggregation( + AggregationBuilders.terms("names") + .field("categories.name") + .size(1000) + ) + ); + + SearchHits searchHits = elasticsearchOperations.search(builder.build(), ProviderIndex.class); + if (searchHits.getAggregations() == null) return new ArrayList<>(); + + org.elasticsearch.search.aggregations.Aggregations aggregations = + (org.elasticsearch.search.aggregations.Aggregations) searchHits.getAggregations().aggregations(); + + Nested nested = aggregations.get("nested_categories"); + Terms terms = nested.getAggregations().get("names"); + + return terms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList()); + } + + // --- helper per aggregazioni semplici --- + private List getAggregatedField(String fieldName, boolean considerAllOrgs) { + NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder().withMaxResults(0); + + if (!considerAllOrgs) { + builder.withQuery(QueryBuilders.rangeQuery("publishedOfferingsCount").gte(1)); + } + + builder.withAggregations( + AggregationBuilders.terms("agg_" + fieldName) + .field(fieldName) + .size(1000) + ); + + SearchHits searchHits = elasticsearchOperations.search(builder.build(), ProviderIndex.class); + if (searchHits.getAggregations() == null) return new ArrayList<>(); + + org.elasticsearch.search.aggregations.Aggregations aggregations = + (org.elasticsearch.search.aggregations.Aggregations) searchHits.getAggregations().aggregations(); + + Terms terms = aggregations.get("agg_" + fieldName); + + return terms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/ProviderService.java b/src/main/java/it/eng/dome/search/service/ProviderService.java deleted file mode 100644 index fb6ee1f..0000000 --- a/src/main/java/it/eng/dome/search/service/ProviderService.java +++ /dev/null @@ -1,304 +0,0 @@ -package it.eng.dome.search.service; - -import it.eng.dome.search.domain.IndexingObject; -import it.eng.dome.search.domain.dto.RelatedPartyDTO; -import it.eng.dome.search.repository.OfferingRepository; -import it.eng.dome.search.service.dto.OrganizationSearchRequest; -import it.eng.dome.tmforum.tmf632.v4.model.Organization; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -@Service -public class ProviderService { - - private static final Logger log = LoggerFactory.getLogger(ProviderService.class); - - @Autowired - private OfferingRepository offeringRepo; - - @Autowired - TmfDataRetriever tmfDataRetriever; - - public Page filterOrganizations(OrganizationSearchRequest request, Pageable pageable) { - if (request == null) { - log.warn("Organization request is null."); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Organization request is null."); -// return Page.empty(pageable); - } - - boolean hasCategories = hasValues(request.getCategories()); - boolean hasCountries = hasValues(request.getCountries()); - boolean hasComplianceLevels = hasValues(request.getComplianceLevels()); - - if (!hasCategories && !hasCountries && !hasComplianceLevels) { -// log.warn("No filter criteria provided"); - log.info("No filter criteria provided, return all providers"); - List all = tmfDataRetriever.getAllPaginatedOrganizations(null, null, 100); - printListLog("All Providers", all, Organization::getId); - return this.paginate(all, pageable); - //throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least one filter (categories or countries) must be provided"); - // change with line below if you want return all providers when the filters are empty. - //return findAllProviders(pageable); // metodo da creare per recuperare tutto - } - - // Lista temporanea per fare l'intersezione dei vari filtri - List> listsToIntersect = new ArrayList<>(); - - if (hasCategories) { - log.info("Searching providers for categories: {}", request.getCategories()); - List byCategory = findOrganizationsByCategories(request.getCategories()); - printListLog("Category Providers", byCategory, Organization::getId); - listsToIntersect.add(byCategory); - } - - if (hasCountries) { - log.info("Searching providers for countries: {}", request.getCountries()); - List byCountry = findOrganizationsByCountry(request.getCountries()); - printListLog("Country Providers", byCountry, Organization::getId); - listsToIntersect.add(byCountry); - } - - if (hasComplianceLevels) { - log.info("Searching providers for compliance levels: {}", request.getComplianceLevels()); - List byCompliance = findOrganizationsByCompliance(request.getComplianceLevels()); - printListLog("Compliance Providers", byCompliance, Organization::getId); - listsToIntersect.add(byCompliance); - } - - // if not are valid filter return empty page - if (listsToIntersect.isEmpty()) { - return Page.empty(pageable); - } - - // Se c'è un solo filtro, non serve fare l'intersezione - if (listsToIntersect.size() == 1) { - List singleResult = listsToIntersect.get(0); - log.info("Single filter applied, returning {} providers", singleResult.size()); - return paginate(singleResult, pageable); - } - - // Intersezione di tutti i filtri selezionati - List intersected = intersectOrganizations(listsToIntersect.toArray(new List[0])); - printListLog("After intersection, Providers", intersected, Organization::getId); - return paginate(intersected, pageable); - } - - private boolean hasValues(List list) { - return list != null && !list.isEmpty(); - } - - private List findOrganizationsByCategories(List categories) { - // retrieve info from offering repo (elasticsearch) - List allObjects = offeringRepo.findByCategoryIdsOrNames(categories); - - if (allObjects.isEmpty()) return Collections.emptyList(); - - // extract unique RelatedParty IDs - Set relatedPartyIds = extractRelatedPartyIds(allObjects, null); - - log.debug("Found {} unique RelatedParty IDs from ProductOfferings", relatedPartyIds.size()); - - return relatedPartyIds.parallelStream() - .map(id -> { - try { - return tmfDataRetriever.getOrganizationById(id, null); - } catch (Exception e) { - log.error("Error retrieving Organization {}: {}", id, e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private List findOrganizationsByCountry(List countries) { - // retrieve all objects with a relatedPartyId from offering repo (elasticsearch) - List allObjects = offeringRepo.findAllWithRelatedParties(); - - if (allObjects.isEmpty()) return Collections.emptyList(); - - // extract unique RelatedParty IDs - Set relatedPartyIds = extractRelatedPartyIds(allObjects, null); - - log.debug("Found {} unique RelatedParty IDs from ProductOfferings {}", relatedPartyIds.size(), relatedPartyIds); - - List allOrg = relatedPartyIds.parallelStream() - .map(id -> { - try { - return tmfDataRetriever.getOrganizationById(id, null); - } catch (Exception e) { - log.error("Error retrieving Organization {}: {}", id, e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - return allOrg.stream() - .filter(org -> org.getPartyCharacteristic() != null && - org.getPartyCharacteristic().stream().anyMatch(ch -> - "country".equalsIgnoreCase(ch.getName()) && - ch.getValue() != null && - countries.stream() - .anyMatch(c -> c.equalsIgnoreCase(ch.getValue().toString())) - )) - .collect(Collectors.toList()); - } - - private List findOrganizationsByCompliance(List complianceLevels) { - if (complianceLevels == null || complianceLevels.isEmpty()) return Collections.emptyList(); - - // Recupera tutti gli IndexingObject che contengono almeno un value tra i complianceLevels - List matchedObjects = offeringRepo.findByComplianceLevels(complianceLevels); - - if (matchedObjects.isEmpty()) return Collections.emptyList(); - - // // extract unique RelatedParty IDs with role Seller or Owner - List allowedRoles = List.of("Seller", "Owner"); - Set relatedPartyIds = extractRelatedPartyIds(matchedObjects, allowedRoles); - - log.debug("Found {} unique RelatedParty IDs by compliance levels", relatedPartyIds.size()); - - // Recupera Organizations da TMF API - return relatedPartyIds.parallelStream() - .map(id -> { - try { - return tmfDataRetriever.getOrganizationById(id, null); - } catch (Exception e) { - log.error("Error retrieving Organization {}: {}", id, e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - @SafeVarargs - private final List intersectOrganizations(List... lists) { - if (lists == null || lists.length == 0) { - return Collections.emptyList(); - } - - // begin with IDs from the first list - Set commonIds = lists[0].stream() - .map(Organization::getId) - .collect(Collectors.toSet()); - - // intersect with IDs from subsequent lists - for (int i = 1; i < lists.length; i++) { - Set ids = lists[i].stream() - .map(Organization::getId) - .collect(Collectors.toSet()); - commonIds.retainAll(ids); // mantiene solo gli ID comuni - } - - // filter the first list to include only organizations with IDs in commonIds - return lists[0].stream() - .filter(o -> commonIds.contains(o.getId())) - .collect(Collectors.toList()); - } - - private Set extractRelatedPartyIds(List objects, List allowedRoles) { - if (objects == null || objects.isEmpty()) return Collections.emptySet(); - - return objects.stream() - .filter(obj -> obj.getRelatedParties() != null) - .flatMap(obj -> obj.getRelatedParties().stream()) - .filter(rp -> rp.getId() != null && !rp.getId().isBlank()) - .filter(rp -> { - // if allowedRoles is null or empty not filter by role - if (allowedRoles == null || allowedRoles.isEmpty()) - return true; - return allowedRoles.stream().anyMatch(role -> role.equalsIgnoreCase(rp.getRole())); - }) - .map(RelatedPartyDTO::getId) - .collect(Collectors.toSet()); - } - - private Page paginate(List organizations, Pageable pageable) { - if (organizations == null || organizations.isEmpty()) { - return Page.empty(pageable); - } - - int start = (int) pageable.getOffset(); - int end = Math.min(start + pageable.getPageSize(), organizations.size()); - - if (start >= end) return Page.empty(pageable); - - return new PageImpl<>(organizations.subList(start, end), pageable, organizations.size()); - } - - private void printListLog(String label, List list, Function idExtractor) { - log.debug("Found {} {}: {}", - list.size(), - label, - list.stream() - .map(idExtractor) - .collect(Collectors.toList())); - } - - - // frontend endpoint populate - public List getAllCategories() { - Set allCategories = new HashSet<>(); - - log.info("Retrieving all categories from TMF..."); - tmfDataRetriever.fetchCategoriesByBatch("name", null, 100, batch -> { - if (batch != null) { - batch.forEach(cat -> { - if (cat.getName() != null && !cat.getName().isBlank()) { - allCategories.add(cat.getName()); - } - }); - } - }); - - List sortedCategories = allCategories.stream() - .sorted() - .collect(Collectors.toList()); - - log.info("Collected {} unique categories: {}", sortedCategories.size(), sortedCategories); - return sortedCategories; - } - - public List getAllCountries() { - Set allCountries = new HashSet<>(); - - log.info("Retrieving all countries from TMF..."); - List allOrg = tmfDataRetriever.getAllPaginatedOrganizations(null, null, 100); - if (allOrg != null) { - for (Organization org : allOrg) { - if (org.getPartyCharacteristic() != null) { - for (var pc : org.getPartyCharacteristic()) { - if ("country".equalsIgnoreCase(pc.getName()) && pc.getValue() != null) { - allCountries.add(pc.getValue().toString()); - } - } - } - } - } - - List sortedCountries = allCountries.stream() - .sorted() - .collect(Collectors.toList()); - - log.info("Collected {} unique countries: {}", sortedCountries.size(), sortedCountries); - return sortedCountries; - } - - public List getAllComplianceLevels() { - return Arrays.asList("BL", "P", "P+"); - } - -} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/ResultProcessor.java b/src/main/java/it/eng/dome/search/service/ResultProcessor.java index be18f6a..91293bb 100644 --- a/src/main/java/it/eng/dome/search/service/ResultProcessor.java +++ b/src/main/java/it/eng/dome/search/service/ResultProcessor.java @@ -1,7 +1,9 @@ package it.eng.dome.search.service; import it.eng.dome.search.domain.IndexingObject; +import it.eng.dome.search.domain.ProviderIndex; import it.eng.dome.tmforum.tmf620.v4.model.ProductOffering; +import it.eng.dome.tmforum.tmf632.v4.model.Organization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -131,4 +133,28 @@ public Page processResultsWithScore(Map, M } } + public Page processProviderResults(Page page, Pageable pageable) { + + Map mapOrganizations = new HashMap<>(); + List resultList = new ArrayList<>(); + + log.info("Total number of Providers: {}", page.getTotalElements()); + + for (ProviderIndex providerIndex : page.getContent()) { + String orgId = providerIndex.getId(); + if (orgId != null && !mapOrganizations.containsKey(orgId)) { + Organization org = tmfDataRetriever.getOrganizationById(orgId, null); + if (org != null) { + mapOrganizations.put(orgId, org); + resultList.add(org); + log.info("Fetched Organization {}: {}", orgId, org.getTradingName()); + } else { + log.warn("Organization {} not found", orgId); + } + } + } + + return new PageImpl<>(resultList, pageable, page.getTotalElements()); + } + } \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/service/dto/CatalogResponse.java b/src/main/java/it/eng/dome/search/service/dto/CatalogResponse.java new file mode 100644 index 0000000..1dd130e --- /dev/null +++ b/src/main/java/it/eng/dome/search/service/dto/CatalogResponse.java @@ -0,0 +1,21 @@ +package it.eng.dome.search.service.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Setter +@Getter +public class CatalogResponse { + + private List category; + + @Setter + @Getter + public static class Category { + private String id; + private String name; + + } +} diff --git a/src/main/java/it/eng/dome/search/service/dto/DomeConfigResponse.java b/src/main/java/it/eng/dome/search/service/dto/DomeConfigResponse.java new file mode 100644 index 0000000..1874c6e --- /dev/null +++ b/src/main/java/it/eng/dome/search/service/dto/DomeConfigResponse.java @@ -0,0 +1,13 @@ +package it.eng.dome.search.service.dto; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Data +public class DomeConfigResponse { + private String defaultId; + +} \ No newline at end of file diff --git a/src/main/java/it/eng/dome/search/web/rest/ProviderResource.java b/src/main/java/it/eng/dome/search/web/rest/ProviderResource.java index ae21dc8..9d9e9b8 100644 --- a/src/main/java/it/eng/dome/search/web/rest/ProviderResource.java +++ b/src/main/java/it/eng/dome/search/web/rest/ProviderResource.java @@ -1,7 +1,10 @@ package it.eng.dome.search.web.rest; +import it.eng.dome.search.domain.ProviderIndex; import it.eng.dome.search.rest.web.util.PaginationUtil; -import it.eng.dome.search.service.ProviderService; +import it.eng.dome.search.service.ProviderIndexingService; +import it.eng.dome.search.service.ProviderProcessor; +import it.eng.dome.search.service.ResultProcessor; import it.eng.dome.search.service.dto.OrganizationSearchRequest; import it.eng.dome.tmforum.tmf632.v4.model.Organization; import org.springframework.beans.factory.annotation.Autowired; @@ -19,33 +22,49 @@ public class ProviderResource { @Autowired - private ProviderService providerService; + private ProviderProcessor providerProcessor; - @PostMapping("/searchOrganizations") - public ResponseEntity> searchOrganizations(@RequestBody OrganizationSearchRequest request, Pageable pageable) { - Page results = providerService.filterOrganizations(request, pageable); - HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(results, "/api/searchOrganizations"); - return new ResponseEntity<>(results.getContent(), headers, HttpStatus.OK); - } + @Autowired + private ResultProcessor resultProcessor; + + @Autowired + private ProviderIndexingService providerIndexingService; // --- GET /categories --- @GetMapping("/categories") - public ResponseEntity> getCategories() { - List categories = providerService.getAllCategories(); + public ResponseEntity> getCategories(@RequestParam(name = "considerAllOrgs", required = false, defaultValue = "false") boolean considerAllOrgs) { + List categories = providerProcessor.getAllCategories(considerAllOrgs); return ResponseEntity.ok(categories); } // --- GET /countries --- @GetMapping("/countries") - public ResponseEntity> getCountries() { - List countries = providerService.getAllCountries(); + public ResponseEntity> getCountries(@RequestParam(name = "considerAllOrgs", required = false, defaultValue = "false") boolean considerAllOrgs) { + List countries = providerProcessor.getAllCountries(considerAllOrgs); return ResponseEntity.ok(countries); } // --- GET /countries --- @GetMapping("/complianceLevels") - public ResponseEntity> getComplianceLevels() { - List complianceLevels = providerService.getAllComplianceLevels(); + public ResponseEntity> getComplianceLevels(@RequestParam(name = "considerAllOrgs", required = false, defaultValue = "false") boolean considerAllOrgs) { + List complianceLevels = providerProcessor.getAllComplianceLevels(considerAllOrgs); return ResponseEntity.ok(complianceLevels); } + + @PostMapping(value = "/SearchOrganizations") + public ResponseEntity> searchOrganizations(@RequestBody OrganizationSearchRequest request, + @RequestParam(name="considerAllOrgs", required = false, defaultValue = "false") boolean considerAllOrgs, + Pageable pageable){ + Page page = providerProcessor.searchProvider(request, considerAllOrgs, pageable); + Page pageProduct = resultProcessor.processProviderResults(page, pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(pageProduct, "/api/searchOrganizations"); + return new ResponseEntity<>(pageProduct.getContent(), headers, HttpStatus.OK); + } + + @GetMapping("/organizations/clearRepository") + public ResponseEntity clearRepository() { + providerIndexingService.clearRepository(); + return ResponseEntity.noContent().build(); + } + } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 205ca5a..b95cf54 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -57,8 +57,11 @@ tmforumapi: timeout: 60 dome: + catalog: + base-url: ${DOME_BASE_URL:https://dome-marketplace-sbx.org} + catalog-path: /catalog/catalog + config-path: /config classify: url: ${CLASSIFY_URL:https://deployenv6.expertcustomers.ai:8086/services/dome/classify} - analyze: - url: ${ANALYZE_URL:https://deployenv6.expertcustomers.ai:8086/services/dome/analyze} + url: ${ANALYZE_URL:https://deployenv6.expertcustomers.ai:8086/services/dome/analyze} \ No newline at end of file