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