From 33a97a6ee4af766cb223be74ad1953b4facc2386 Mon Sep 17 00:00:00 2001 From: "fabio.d.mota" Date: Fri, 5 Apr 2024 16:02:24 +0100 Subject: [PATCH] feat(backend): Enhance sequential negotiation process with error handling and caching --- CHANGELOG.md | 16 + pom.xml | 2 +- .../config/EdcProperties.java | 38 +++ .../config/SecurityConfiguration.java | 2 +- .../dto/edc/CatalogItemDTO.java | 48 +++ .../{EDRResponse.java => EDRResponseDTO.java} | 2 +- .../dto/edc/NegotiationRequestDTO.java | 39 +++ .../dto/edc/NegotiationResponseDTO.java | 53 ++++ .../service/CacheEvictService.java | 9 + .../service/logic/EDCLogicService.java | 294 ------------------ .../service/logic/EdcLogicService.java | 164 ++++++++++ .../service/logic/InvokeService.java | 27 +- .../logic/NegotiationServiceLogic.java | 227 ++++++++++++++ .../service/logic/RequestLogicService.java | 37 ++- .../utils/EdcEndpointsMappingUtils.java | 155 +++++++++ .../web/rest/NegotiationController.java | 80 +++++ src/main/resources/config/application.yml | 4 +- 17 files changed, 890 insertions(+), 307 deletions(-) create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/config/EdcProperties.java create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/CatalogItemDTO.java rename src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/{EDRResponse.java => EDRResponseDTO.java} (97%) create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationRequestDTO.java create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationResponseDTO.java delete mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EDCLogicService.java create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EdcLogicService.java create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/NegotiationServiceLogic.java create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/utils/EdcEndpointsMappingUtils.java create mode 100644 src/main/java/org/eclipse/tractusx/valueaddedservice/web/rest/NegotiationController.java diff --git a/CHANGELOG.md b/CHANGELOG.md index bb82f07..8fe4dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Changelog +## [2.0.0] - [unreleased] + +## Added +- Implement `triggerNegotiation` function in `NegotiationServiceLogic` to handle sequential negotiation requests with external services, enhancing the negotiation process with error handling and response transformation. +- Introduce new DTOs (`NegotiationRequestDTO`, `NegotiationResponseDTO`, `EDRResponseDTO`) to streamline the handling of negotiation data and responses. +- Add utility functions in `EdcEndpointsMappingUtils` for parsing and extracting specific fields from JSON responses, improving data extraction reliability and code maintainability. + +## Changed +- Modify `executeSequentialNegotiationRequests` logic to include additional steps in the negotiation process, ensuring the correct sequence of requests and proper handling of intermediate responses. +- Update error handling across the negotiation process to log detailed error messages and fallback values, improving debugging and reliability. +- Refactor `createNegotiationRequestBody` to dynamically generate request bodies based on input parameters, enhancing flexibility and readability. + +## Fixed +- Address issue with incorrect extraction of `transferProcessId` by adjusting JSON path in `extractTransferProcessId` function. + + ## [1.3.2] - [2024-04-17] ### Changed diff --git a/pom.xml b/pom.xml index 08522a0..222c4e4 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ org.eclipse.tractusx value-added-service - 1.3.2 + 2.0.0 vas-country-risk-backend Project to Validate Country Risks Score diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/config/EdcProperties.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/config/EdcProperties.java new file mode 100644 index 0000000..f6288cd --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/config/EdcProperties.java @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Setter +@Getter +@Configuration +@ConfigurationProperties(prefix = "application.edc") +public class EdcProperties { + + private List providers; + + +} diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/config/SecurityConfiguration.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/config/SecurityConfiguration.java index 227f0b4..7198b19 100644 --- a/src/main/java/org/eclipse/tractusx/valueaddedservice/config/SecurityConfiguration.java +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/config/SecurityConfiguration.java @@ -44,7 +44,7 @@ public class SecurityConfiguration { public SecurityFilterChain securityFilterChain(final HttpSecurity httpSecurity) throws Exception { httpSecurity.cors(withDefaults()) .authorizeHttpRequests((auth-> auth - .requestMatchers("/error","/api/dashboard/**","/api/sharing/**","/api/edc/**") + .requestMatchers("/error","/api/dashboard/**","/api/sharing/**","/api/negotiation/**") .authenticated() .requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**","/management/**") .permitAll() diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/CatalogItemDTO.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/CatalogItemDTO.java new file mode 100644 index 0000000..05fca5a --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/CatalogItemDTO.java @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.dto.edc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Setter +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Represents a catalog item available for negotiation") +public class CatalogItemDTO { + + @Schema(description = "Unique identifier of the catalog item", example = "1", required = true) + private String id; + + @Schema(description = "Identifier of the offer associated with the catalog item", example = "offer123", required = true) + private String offerId; + + @Schema(description = "Provider of the catalog item", example = "Provider A") + private String provider; + + @Schema(description = "Subject of the catalog item", example = "cx-taxo:ReadAccessPoolForCatenaXMember") + private String subject; + + @Schema(description = "Description of the catalog item", example = "Grants the Catena-X Member read access to the Pool API...") + private String description; +} + diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/EDRResponse.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/EDRResponseDTO.java similarity index 97% rename from src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/EDRResponse.java rename to src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/EDRResponseDTO.java index 09e5e57..6da4c8c 100644 --- a/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/EDRResponse.java +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/EDRResponseDTO.java @@ -26,7 +26,7 @@ @Setter @Getter @ToString -public class EDRResponse { +public class EDRResponseDTO { private String authCode; private String endpoint; diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationRequestDTO.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationRequestDTO.java new file mode 100644 index 0000000..201c590 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationRequestDTO.java @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.dto.edc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Setter +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Data Transfer Object for initiating a negotiation request") +public class NegotiationRequestDTO { + + @Schema(description = "Unique identifier of the catalog item", example = "1", required = true) + private String id; + + @Schema(description = "Identifier of the offer associated with the catalog item", example = "offer123", required = true) + private String offerId; + +} diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationResponseDTO.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationResponseDTO.java new file mode 100644 index 0000000..262cb7e --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/dto/edc/NegotiationResponseDTO.java @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.dto.edc; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Setter +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Data Transfer Object with negotiation status") +public class NegotiationResponseDTO { + + @Schema(description = "Unique identifier of the catalog item", example = "1", required = true) + private String id; + + @Schema(description = "Identifier of the offer associated with the catalog item", example = "offer123", required = true) + private String offerId; + + @Schema(description = "Provider of the catalog item", example = "Provider A") + private String provider; + + @Schema(description = "Status of negotiation of the catalog item", example = "Negotiated") + private String status; + + @JsonIgnore + @Schema(description = "Auth Code for requesting the endpoint", example = "utasdbvhsarpoausighasd") + private String authCode; + + @JsonIgnore + @Schema(description = "Endpoint for the Final Request", example = "http://localhost:80/finalRequest") + private String endpoint; +} diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/CacheEvictService.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/CacheEvictService.java index 20cab96..fba5e65 100644 --- a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/CacheEvictService.java +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/CacheEvictService.java @@ -47,6 +47,9 @@ public class CacheEvictService { @Autowired ReportLogicService reportLogicService; + @Autowired + NegotiationServiceLogic negotiationServiceLogic; + @Scheduled(fixedRate = ONE_HOUR) public void clearCacheCountry() { countryLogicService.invalidateAllCache(); @@ -76,4 +79,10 @@ public void clearCacheReports() { reportLogicService.invalidateAllCache(); log.info("Cache for Reports cleared."); } + + @Scheduled(fixedRate = ONE_MINUTE*1) + public void clearCacheNegotiation() { + negotiationServiceLogic.invalidateAllCache(); + log.info("Cache for Negotiation cleared."); + } } diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EDCLogicService.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EDCLogicService.java deleted file mode 100644 index c6a94a5..0000000 --- a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EDCLogicService.java +++ /dev/null @@ -1,294 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022,2024 BMW Group AG - * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -package org.eclipse.tractusx.valueaddedservice.service.logic; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.valueaddedservice.dto.edc.EDRResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Service; -import reactor.core.publisher.Mono; - -import java.io.IOException; -import java.time.Duration; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -@Service -@Slf4j -public class EDCLogicService { - - @Autowired - private InvokeService invokeService; - - @Value("${application.bpdm.consumerManagementUrl}") - private String consumerManagementUrl; - - @Value("${application.bpdm.gateProviderProtocolUrl}") - private String gateProviderProtocolUrl; - - @Value("${application.bpdm.gateProviderId}") - private String gateProviderId; - - @Value("${application.bpdm.policyBpn}") - private String policyBpn; - - @Value("${application.bpdm.apiKey}") - private String apiKey; - - @Autowired - ObjectMapper objectMapper; - - public Mono executeSequentialRequests(String assetId,Object body) { - Map response = queryCatalog(); - String offerId = response.get("ASSET_" + assetId); - - if (offerId == null) { - log.error("Offer ID is missing"); - return Mono.error(new RuntimeException("Asset ID or Offer ID is missing")); - } - - return retrieveEDRsData(assetId) - .flatMap(lastNegotiatedTransferProcessId -> { - if (lastNegotiatedTransferProcessId.isEmpty()) { - log.info("No negotiated transfer process ID found"); - log.info("Initiating Negotiation"); - return sendNegotiationInitiateRequest(offerId, assetId) - .then(Mono.delay(Duration.ofSeconds(3))) // Non-blocking delay - .then(retrieveEDRsData(assetId)) - .flatMap(this::getEDRById) - .flatMap(responseFromEDRById -> sendFinalRequest(responseFromEDRById, body)); - } else { - log.debug("Found negotiated transfer process ID"); - return getEDRById(lastNegotiatedTransferProcessId) - .flatMap(responseFromEDRById -> sendFinalRequest(responseFromEDRById, body)); - } - }); - } - - public Mono getEDRById(String transferProcessId) { - return executeGetRequest(consumerManagementUrl + "/edrs/" + transferProcessId, this::parseEDRResponse); - } - - public Mono sendFinalRequest(EDRResponse edrResponse, Object body) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("Authorization", edrResponse.getAuthCode()); - return executePostRequest(edrResponse.getEndpoint(), body, headers, response -> response); - } - - - public Mono retrieveEDRsData(String assetId) { - return executeGetRequest(consumerManagementUrl + "/edrs?assetId=" + assetId, this::extractLastNegotiatedTransferProcessId); - } - - public Map queryCatalog() { - HttpHeaders headers = createHttpHeaders(); - Map requestBody = createCatalogRequestBody(); - HttpEntity> httpEntity = new HttpEntity<>(requestBody, headers); - - log.debug("Sending POST request to URL: " + consumerManagementUrl + "/v2/catalog/request/"); - log.debug("Request Headers: " + headers.toString()); - log.debug("Request Body: " + requestBody.toString()); - - return invokeService.executeRequest("default",consumerManagementUrl + "/v2/catalog/request/", HttpMethod.POST, httpEntity, this::mapResponseFromQueryCatalog).block(); - } - - public Mono sendNegotiationInitiateRequest(String offerId, String assetId) { - HttpHeaders headers = createHttpHeaders(); - Map requestBody = createNegotiationRequestBody(offerId, assetId); - HttpEntity> httpEntity = new HttpEntity<>(requestBody, headers); - - // Log the URL, Headers, and Body - String url = consumerManagementUrl + "/edrs"; - log.debug("Sending POST request to URL: " + url); - log.debug("Request Headers: " + headers.toString()); - log.debug("Request Body: " + requestBody.toString()); - - return invokeService.executeRequest("default",consumerManagementUrl + "/edrs", HttpMethod.POST, httpEntity); - } - - // Helper methods - - private Mono executeGetRequest(String url, Function responseMapper) { - HttpHeaders headers = createHttpHeaders(); - HttpEntity> httpEntity = new HttpEntity<>(new HashMap<>(), headers); - - return invokeService.executeRequest("default",url, HttpMethod.GET, httpEntity, responseMapper); - } - - private Mono executePostRequest(String url, Object body, HttpHeaders headers, Function responseMapper) { - HttpEntity httpEntity = new HttpEntity<>(body, headers); - return invokeService.executeRequest("default",url, HttpMethod.POST, httpEntity, responseMapper); - } - - - private HttpHeaders createHttpHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("X-Api-Key", apiKey); - return headers; - } - - private Map createCatalogRequestBody() { - Map requestBody = new HashMap<>(); - requestBody.put("@context", new HashMap<>()); - requestBody.put("protocol", "dataspace-protocol-http"); - requestBody.put("providerUrl", gateProviderProtocolUrl); - - Map querySpec = new HashMap<>(); - querySpec.put("offset", 0); - querySpec.put("limit", 100); - querySpec.put("filter", ""); - - Map range = new HashMap<>(); - range.put("from", 0); - range.put("to", 100); - querySpec.put("range", range); - querySpec.put("criterion", ""); - - requestBody.put("querySpec", querySpec); - return requestBody; - } - - private Map createNegotiationRequestBody(String offerId, String assetId) { - Map body = new HashMap<>(); - body.put("@context", Collections.singletonMap("odrl", "http://www.w3.org/ns/odrl/2/")); - body.put("@type", "NegotiationInitiateRequestDto"); - body.put("connectorAddress", gateProviderProtocolUrl); - body.put("protocol", "dataspace-protocol-http"); - body.put("connectorId", gateProviderId); - body.put("providerId", gateProviderId); - - Map offer = new HashMap<>(); - offer.put("offerId", offerId); - offer.put("assetId", assetId); - - Map policy = new HashMap<>(); - policy.put("@type", "odrl:Set"); - - Map permission = new HashMap<>(); - permission.put("odrl:target", assetId); - permission.put("odrl:action", Collections.singletonMap("odrl:type", "USE")); - - Map constraint = new HashMap<>(); - Map orConstraint = new HashMap<>(); - orConstraint.put("odrl:leftOperand", "BusinessPartnerNumber"); - orConstraint.put("odrl:operator", Collections.singletonMap("@id", "odrl:eq")); - orConstraint.put("odrl:rightOperand", policyBpn); - constraint.put("odrl:or", orConstraint); - - permission.put("odrl:constraint", constraint); - policy.put("odrl:permission", permission); - - policy.put("odrl:prohibition", Collections.emptyList()); - policy.put("odrl:obligation", Collections.emptyList()); - policy.put("odrl:target",assetId); - - offer.put("policy", policy); - body.put("offer", offer); - return body; - } - - private EDRResponse parseEDRResponse(String jsonResponse) { - ObjectMapper objectMapper = new ObjectMapper(); - try { - JsonNode rootNode = objectMapper.readTree(jsonResponse); - EDRResponse response = new EDRResponse(); - if (rootNode.has("edc:authCode")) { - response.setAuthCode(rootNode.get("edc:authCode").asText()); - } - if (rootNode.has("edc:endpoint")) { - response.setEndpoint(rootNode.get("edc:endpoint").asText()); - } - return response; - } catch (IOException e) { - log.error("Error parsing JSON: {}", e.getMessage()); - throw new RuntimeException("Error Getting authCode or Endpoint"); - } - } - - private Map mapResponseFromQueryCatalog(String response) { - Map assetOfferMap = new HashMap<>(); - ObjectMapper objectMapper = new ObjectMapper(); - try { - JsonNode responseJson = objectMapper.readTree(response); - JsonNode datasets = responseJson.path("dcat:dataset"); - - if (datasets.isArray()) { - for (JsonNode dataset : datasets) { - processDatasetAndAddToMap(dataset, assetOfferMap); - } - } else if (!datasets.isMissingNode()) { - processDatasetAndAddToMap(datasets, assetOfferMap); - } - } catch (IOException e) { - log.error("Error parsing response JSON: {}", e.getMessage()); - } - - return assetOfferMap; - } - - private String extractLastNegotiatedTransferProcessId(String jsonResponse) { - ObjectMapper objectMapper = new ObjectMapper(); - String lastNegotiatedTransferProcessId = ""; - - try { - JsonNode rootNode = objectMapper.readTree(jsonResponse); - if (rootNode.isArray()) { - for (JsonNode node : rootNode) { - if (node.has("tx:edrState") && "NEGOTIATED".equals(node.get("tx:edrState").asText())) { - if (node.has("edc:transferProcessId")) { - lastNegotiatedTransferProcessId = node.get("edc:transferProcessId").asText(); - } - } - } - } else { - log.info("Response is not an array."); - } - } catch (IOException e) { - log.error("Error parsing JSON: {}", e.getMessage()); - } - - if (lastNegotiatedTransferProcessId.isEmpty()) { - log.info("No negotiated transfer process ID found."); - } - - return lastNegotiatedTransferProcessId; - } - - - private void processDatasetAndAddToMap(JsonNode dataset, Map map) { - if (dataset.has("@id") && dataset.has("odrl:hasPolicy") && dataset.get("odrl:hasPolicy").has("@id")) { - String id = dataset.get("@id").asText(); - String offerId = dataset.get("odrl:hasPolicy").get("@id").asText(); - map.put("ASSET_" + id, offerId); - } - } - -} diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EdcLogicService.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EdcLogicService.java new file mode 100644 index 0000000..3219f22 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/EdcLogicService.java @@ -0,0 +1,164 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.service.logic; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.valueaddedservice.config.EdcProperties; +import org.eclipse.tractusx.valueaddedservice.dto.edc.CatalogItemDTO; +import org.eclipse.tractusx.valueaddedservice.dto.edc.NegotiationResponseDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@Service +@Slf4j +public class EdcLogicService { + + @Autowired + private InvokeService invokeService; + + @Value("${application.bpdm.consumerManagementUrl}") + private String consumerManagementUrl; + + @Value("${application.bpdm.gateProviderProtocolUrl}") + private String gateProviderProtocolUrl; + + @Value("${application.bpdm.apiKey}") + private String apiKey; + + @Autowired + private EdcProperties edcProperties; + + @Autowired + ObjectMapper objectMapper; + + + + public Mono sendFinalRequest(NegotiationResponseDTO edrResponse, Object body) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", edrResponse.getAuthCode()); + return executePostRequest(edrResponse.getEndpoint(), body, headers, response -> response); + } + + + public List queryCatalog() { + HttpHeaders headers = createHttpHeaders(); + Map requestBody = createCatalogRequestBody(); + HttpEntity> httpEntity = new HttpEntity<>(requestBody, headers); + + log.debug("Sending POST request to URL: " + consumerManagementUrl + "/v2/catalog/request/"); + log.debug("Request Headers: " + headers); + log.debug("Request Body: " + requestBody); + + + return invokeService.executeRequest("default",consumerManagementUrl + "/v2/catalog/request/", HttpMethod.POST, httpEntity, this::mapResponseFromQueryCatalog).block(); + } + + // Helper methods + private Mono executePostRequest(String url, Object body, HttpHeaders headers, Function responseMapper) { + HttpEntity httpEntity = new HttpEntity<>(body, headers); + return invokeService.executeRequest("default",url, HttpMethod.POST, httpEntity, responseMapper); + } + + private HttpHeaders createHttpHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-Api-Key", apiKey); + return headers; + } + + private Map createCatalogRequestBody() { + Map requestBody = new HashMap<>(); + requestBody.put("@context", new HashMap<>()); + requestBody.put("protocol", "dataspace-protocol-http"); + requestBody.put("providerUrl", gateProviderProtocolUrl); + + Map querySpec = new HashMap<>(); + querySpec.put("offset", 0); + querySpec.put("limit", 100); + querySpec.put("filter", ""); + + Map range = new HashMap<>(); + range.put("from", 0); + range.put("to", 100); + querySpec.put("range", range); + querySpec.put("criterion", ""); + + requestBody.put("querySpec", querySpec); + return requestBody; + } + + + private List mapResponseFromQueryCatalog(String response) { + List catalogItems = new ArrayList<>(); + ObjectMapper objectMapper = new ObjectMapper(); + try { + JsonNode responseJson = objectMapper.readTree(response); + JsonNode datasets = responseJson.path("dcat:dataset"); + + if (datasets.isArray()) { + datasets.forEach(dataset -> { + String type = dataset.path("dct:type").asText().replace("cx-taxo:", ""); + if (edcProperties.getProviders().contains(type)) { + catalogItems.add(processDatasetAndCreateDTO(dataset)); + } + }); + } else if (!datasets.isMissingNode()) { + String type = datasets.path("dct:type").asText().replace("cx-taxo:", ""); + if (edcProperties.getProviders().contains(type)) { + catalogItems.add(processDatasetAndCreateDTO(datasets)); + } + } + } catch (IOException e) { + log.error("Error parsing response JSON: {}", e.getMessage()); + } + + return catalogItems; + } + + private CatalogItemDTO processDatasetAndCreateDTO(JsonNode dataset) { + String id = dataset.get("@id").asText(); + String offerId = dataset.path("odrl:hasPolicy").get("@id").asText(); + String subject = dataset.path("dct:subject").asText().replace("cx-taxo:",""); + String description = dataset.path("dct:description").asText(); + String provider = dataset.path("dct:type").asText().replace("cx-taxo:",""); + + return new CatalogItemDTO(id, offerId, provider, subject, description); + } + + + + +} diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/InvokeService.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/InvokeService.java index aebd42b..e4a8cf8 100644 --- a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/InvokeService.java +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/InvokeService.java @@ -56,6 +56,12 @@ public Mono> executeRequest(String clientType,String url, HttpMethod .body(BodyInserters.fromValue(httpEntity.getBody())) .retrieve() .bodyToMono(String.class) + .doOnSubscribe(subscription -> { + // Logging request details + log.debug("Sending request to URL: {}", url); + log.debug("Request Headers: {}", httpEntity.getHeaders()); + log.debug("Request Body: {}", httpEntity.getBody()); + }) .map(mappingFunction) .onErrorResume(e -> { log.error("error url {} message {}", url, e.getMessage()); @@ -71,11 +77,18 @@ public Mono executeRequest(String clientType,String url, HttpMethod httpM .body(BodyInserters.fromValue(httpEntity.getBody())) .retrieve() .bodyToMono(String.class) + .doOnSubscribe(subscription -> { + // Logging request details + log.debug("Sending request to URL: {}", url); + log.debug("Request Headers: {}", httpEntity.getHeaders()); + log.debug("Request Body: {}", httpEntity.getBody()); + }) .map(mappingFunction) .onErrorResume(e -> { - log.error("error url {} message {}", url, e.getMessage()); + log.error("Error url {} message {}", url, e.getMessage()); throw new RuntimeException(e.getMessage()); }); + } public Mono executeRequest(String clientType,String url, HttpMethod httpMethod, HttpEntity httpEntity) { @@ -100,6 +113,12 @@ public Mono> executeRequest(String clientType,String url, HttpMethod .body(BodyInserters.fromValue(httpEntity.getBody())) .retrieve() .bodyToMono(String.class) + .doOnSubscribe(subscription -> { + // Logging request details + log.debug("Sending request to URL: {}", url); + log.debug("Request Headers: {}", httpEntity.getHeaders()); + log.debug("Request Body: {}", httpEntity.getBody()); + }) .map(mappingFunction) .onErrorResume(e -> { log.error("error url {} message {}", url, e.getMessage()); @@ -126,6 +145,12 @@ public Mono> executeRequest(String url, HttpMethod httpMethod, HttpE .body(BodyInserters.fromValue(httpEntity.getBody())) .retrieve() .bodyToMono(String.class) + .doOnSubscribe(subscription -> { + // Logging request details + log.debug("Sending request to URL: {}", url); + log.debug("Request Headers: {}", httpEntity.getHeaders()); + log.debug("Request Body: {}", httpEntity.getBody()); + }) .map(mappingFunction) .onErrorResume(e -> { log.error("error url {} message {}", url, e.getMessage()); diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/NegotiationServiceLogic.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/NegotiationServiceLogic.java new file mode 100644 index 0000000..a78b5a2 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/NegotiationServiceLogic.java @@ -0,0 +1,227 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.service.logic; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.valueaddedservice.dto.edc.EDRResponseDTO; +import org.eclipse.tractusx.valueaddedservice.dto.edc.NegotiationRequestDTO; +import org.eclipse.tractusx.valueaddedservice.dto.edc.NegotiationResponseDTO; +import org.eclipse.tractusx.valueaddedservice.utils.EdcEndpointsMappingUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@Service +@Slf4j +public class NegotiationServiceLogic { + + + @Autowired + private InvokeService invokeService; + + @Value("${application.bpdm.gateProviderProtocolUrl}") + private String gateProviderProtocolUrl; + + @Value("${application.bpdm.consumerManagementUrl}") + private String consumerManagementUrl; + + @Value("${application.bpdm.policyBpn}") + private String policyBpn; + + @Value("${application.bpdm.apiKey}") + private String apiKey; + + @Value("${application.bpdm.gateProviderId}") + private String gateProviderId; + + private final ConcurrentHashMap negotiationCache = new ConcurrentHashMap<>(); + + + @Cacheable(value = "vas-bpdm-negotiation", key = "{#root.methodName, #negotiationItems}", unless = "#result == null or #result.isEmpty()") + public List triggerNegotiation(List negotiationItems) { + log.info("Triggering negotiation for items: {}", negotiationItems); + + List responses = Flux.fromIterable(negotiationItems) + .flatMap(dto -> + executeSequentialNegotiationRequests(dto.getId(), dto.getOfferId()) + .map(response -> new NegotiationResponseDTO(dto.getId(), dto.getOfferId(), gateProviderProtocolUrl, "Success", response.getAuthCode(), response.getEndpoint())) + .onErrorResume(e -> Mono.just(new NegotiationResponseDTO(dto.getId(), dto.getOfferId(), gateProviderProtocolUrl, "Error", null, null))) + ).collectList().block(); + + responses.stream().forEach(dto -> negotiationCache.put(dto.getId(), dto)); + + return responses; + + } + + public ConcurrentHashMap getStoredNegotiation() { + return negotiationCache; + } + + public Mono executeSequentialNegotiationRequests(String assetId, String offerId) { + + if (offerId == null) { + log.error("Offer ID is missing"); + return Mono.error(new RuntimeException("Asset ID or Offer ID is missing")); + } + + return retrieveEDRsData(assetId) + .flatMap(lastNegotiatedTransferProcessId -> { + if (lastNegotiatedTransferProcessId.isEmpty()) { + log.info("No negotiated transfer process ID found"); + log.info("Initiating Negotiation"); + return sendNegotiationInitiateRequest(offerId, assetId) + .delayElement(Duration.ofSeconds(3)) + .flatMap(this::executeGetRequestForNegotiationDetails) // Returns contractAgreementId + .delayElement(Duration.ofSeconds(3)) + .flatMap(this::executeGetRequestWithAgreementId) // Returns transferProcessId + .delayElement(Duration.ofSeconds(3)) + .flatMap(this::getAuthCodeAndEndpoint); // Returns authCode and endpoint + + } else { + log.debug("Found negotiated transfer process ID"); + return getAuthCodeAndEndpoint(lastNegotiatedTransferProcessId); + } + }); + } + + + public Mono sendNegotiationInitiateRequest(String offerId, String assetId) { + HttpHeaders headers = createHttpHeaders(); + Map requestBody = createNegotiationRequestBody(offerId, assetId); + HttpEntity> httpEntity = new HttpEntity<>(requestBody, headers); + + // Log the URL, Headers, and Body + String url = consumerManagementUrl + "/edrs"; + log.debug("Sending POST request to URL: " + url); + log.debug("Request Headers: " + headers.toString()); + log.debug("Request Body: " + requestBody.toString()); + + return invokeService.executeRequest("default", url, HttpMethod.POST, httpEntity, EdcEndpointsMappingUtils::extractNegotiationIdFromInitialRequest); + } + + public Mono getAuthCodeAndEndpoint(String transferProcessId) { + return executeGetRequest(consumerManagementUrl + "/edrs/" + transferProcessId, EdcEndpointsMappingUtils::getAuthCodeAndEndpoint); + } + + @CacheEvict(value = "vas-bpdm-negotiation", allEntries = true) + public void invalidateAllCache() { + negotiationCache.clear(); + log.debug("invalidateAllCache|vas-bpdm-negotiation - invalidated cache - allEntries"); + } + + public Mono executeGetRequestForNegotiationDetails(String negotiationId) { + String url = consumerManagementUrl + "/v2/contractnegotiations/" + negotiationId; + HttpHeaders headers = createHttpHeaders(); + Map requestBody = new HashMap<>(); + HttpEntity> httpEntity = new HttpEntity<>(requestBody, headers); + + return invokeService.executeRequest("default", url, HttpMethod.GET, httpEntity, EdcEndpointsMappingUtils::extractContractAgreementId) + .doOnError(error -> log.error("Failed to retrieve contract negotiation details", error)); + } + + public Mono executeGetRequestWithAgreementId(String contractAgreementId) { + String url = consumerManagementUrl + "/edrs?agreementId=" + contractAgreementId; + HttpHeaders headers = createHttpHeaders(); + Map requestBody = new HashMap<>(); + HttpEntity> httpEntity = new HttpEntity<>(requestBody, headers); + + return invokeService.executeRequest("default", url, HttpMethod.GET, httpEntity, EdcEndpointsMappingUtils::extractTransferProcessId) + .doOnError(error -> log.error("Failed to make request with agreement ID: {}", contractAgreementId, error)); + } + + + public Map createNegotiationRequestBody(String offerId, String assetId) { + Map body = new HashMap<>(); + body.put("@context", Collections.singletonMap("odrl", "http://www.w3.org/ns/odrl/2/")); + body.put("@type", "NegotiationInitiateRequestDto"); + body.put("counterPartyAddress", gateProviderProtocolUrl); + body.put("protocol", "dataspace-protocol-http"); + body.put("counterPartyId", gateProviderId); + body.put("providerId", gateProviderId); + + Map offer = new HashMap<>(); + offer.put("offerId", offerId); + offer.put("assetId", assetId); + + Map policy = new HashMap<>(); + policy.put("@type", "odrl:Set"); + + Map permission = new HashMap<>(); + permission.put("odrl:target", assetId); + permission.put("odrl:action", Collections.singletonMap("odrl:type", "USE")); + + Map constraint = new HashMap<>(); + Map orConstraint = new HashMap<>(); + orConstraint.put("odrl:leftOperand", "BusinessPartnerNumber"); + orConstraint.put("odrl:operator", Collections.singletonMap("@id", "odrl:eq")); + orConstraint.put("odrl:rightOperand", policyBpn); // Use the specific business partner number here + constraint.put("odrl:or", orConstraint); + + permission.put("odrl:constraint", constraint); + policy.put("odrl:permission", permission); + + policy.put("odrl:prohibition", Collections.emptyList()); + policy.put("odrl:obligation", Collections.emptyList()); + policy.put("odrl:target", assetId); + + offer.put("policy", policy); + body.put("offer", offer); + return body; + } + + public Mono retrieveEDRsData(String assetId) { + + return executeGetRequest(consumerManagementUrl + "/edrs?assetId=" + assetId, EdcEndpointsMappingUtils::extractLastNegotiatedTransferProcessId); + } + + + private Mono executeGetRequest(String url, Function responseMapper) { + HttpHeaders headers = createHttpHeaders(); + HttpEntity> httpEntity = new HttpEntity<>(new HashMap<>(), headers); + + return invokeService.executeRequest("default", url, HttpMethod.GET, httpEntity, responseMapper); + } + + private HttpHeaders createHttpHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-Api-Key", apiKey); + return headers; + } + + +} diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/RequestLogicService.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/RequestLogicService.java index d2db769..1d355fe 100644 --- a/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/RequestLogicService.java +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/service/logic/RequestLogicService.java @@ -27,6 +27,7 @@ import org.eclipse.tractusx.valueaddedservice.dto.bpdm.pool.PoolAddressDto; import org.eclipse.tractusx.valueaddedservice.dto.bpdm.pool.PoolLegalEntityDto; import org.eclipse.tractusx.valueaddedservice.dto.bpdm.pool.PoolSiteDto; +import org.eclipse.tractusx.valueaddedservice.dto.edc.NegotiationResponseDTO; import org.eclipse.tractusx.valueaddedservice.utils.BpdmEndpointsMappingUtils; import org.eclipse.tractusx.valueaddedservice.utils.JsonMappingUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +40,7 @@ import org.springframework.stereotype.Service; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; @Service @Slf4j @@ -46,7 +48,7 @@ public class RequestLogicService { @Autowired - EDCLogicService edcLogicService; + EdcLogicService edcLogicService; @Value("${application.edc.enabled:}") private boolean sequentialRequestsEnabled; @@ -66,29 +68,48 @@ public class RequestLogicService { @Autowired InvokeService invokeService; + @Autowired + NegotiationServiceLogic negotiationServiceLogic; - @Cacheable(value = "vas-bpdm", key = "{#root.methodName , {#roles}}", unless = "#result == null") + @Cacheable(value = "vas-bpdm", key = "#root.methodName + ',' + #roles", unless = "#result == null or #result.isEmpty()") public List handleRequestsToBpdm(List roles) { List finalDtoList = new ArrayList<>(); if (sequentialRequestsEnabled) { - finalDtoList.addAll(handleSequentialRequests()); + // Check the negotiation cache for stored requests + ConcurrentHashMap negotiationRequestDTOS = negotiationServiceLogic.getStoredNegotiation(); + if (negotiationRequestDTOS.isEmpty()) { + log.error("No negotiation requests found in cache."); + return Collections.emptyList(); + } + // Handle sequential requests by passing the map + finalDtoList.addAll(handleSequentialRequests(negotiationRequestDTOS)); } else { finalDtoList.addAll(handleNonSequentialRequests()); } return finalDtoList; } - private List handleSequentialRequests() { + private List handleSequentialRequests(ConcurrentHashMap negotiationResponseDTOS) { List finalDtoList = new ArrayList<>(); + // Check for the presence of required keys in the negotiationResponseDTOS map + List requiredKeys = Arrays.asList("POST_GENERIC_OUTPUT_SEARCH", "POST_BPL_POOL_SEARCH", "POST_BPS_POOL_SEARCH", "POST_BPA_POOL_SEARCH"); + for (String key : requiredKeys) { + if (!negotiationResponseDTOS.containsKey(key)) { + log.error("Missing required negotiation response DTO for key: {}", key); + return Collections.emptyList(); // Return an empty list immediately if a key is missing + } + } + log.info("Sequential requests enabled. Starting process to fetch external business partners from generic."); - String genericEndPointResponse = edcLogicService.executeSequentialRequests("POST_GENERIC_OUTPUT_SEARCH", Collections.emptyList()).block(); + + String genericEndPointResponse = edcLogicService.sendFinalRequest(negotiationResponseDTOS.get("POST_GENERIC_OUTPUT_SEARCH"), Collections.emptyList()).block(); Map>> map = processBusinessPartners(JsonMappingUtils.mapContentToListOfBusinessPartnerOutputDto(genericEndPointResponse)); log.info("Processed business partners from generic endpoint"); log.info("Starting process to fetch external business partners from legal entity on pool."); List bpnlList = getBpnsByAddressType(map, AddressType.LegalAddress); - String bpnl = edcLogicService.executeSequentialRequests("POST_BPL_POOL_SEARCH", bpnlList).block(); + String bpnl = edcLogicService.sendFinalRequest(negotiationResponseDTOS.get("POST_BPL_POOL_SEARCH"), bpnlList).block(); List poolLegalEntityDtos = JsonMappingUtils.mapToListOfPoolLegalEntityDto(bpnl); log.info("Processed business partners from legal entity on pool, list size {}", poolLegalEntityDtos.size()); @@ -96,7 +117,7 @@ private List handleSequentialRequests() { List bpnsList = getBpnsByAddressType(map, AddressType.SiteMainAddress); Map> sitesBody = new HashMap<>(); sitesBody.put("sites", bpnsList); - String bpns = edcLogicService.executeSequentialRequests("POST_BPS_POOL_SEARCH", sitesBody).block(); + String bpns = edcLogicService.sendFinalRequest(negotiationResponseDTOS.get("POST_BPS_POOL_SEARCH"), sitesBody).block(); List poolSiteDtoList = JsonMappingUtils.mapJsonToListOfPoolSiteDto(bpns); List bpnaList = getBpnsByAddressType(map, AddressType.AdditionalAddress); log.info("Processed business partners from site on pool, list size {}", poolSiteDtoList.size()); @@ -104,7 +125,7 @@ private List handleSequentialRequests() { log.info("Starting process to fetch external business partners from address on pool."); Map> addressesBody = new HashMap<>(); addressesBody.put("addresses", bpnaList); - String bpna = edcLogicService.executeSequentialRequests("POST_BPA_POOL_SEARCH", addressesBody).block(); + String bpna = edcLogicService.sendFinalRequest(negotiationResponseDTOS.get("POST_BPA_POOL_SEARCH"), addressesBody).block(); List poolAddressDtos = JsonMappingUtils.mapJsonToListOfPoolAddressDto(bpna); log.info("Processed business partners from address on pool, list size {}", poolAddressDtos.size()); diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/utils/EdcEndpointsMappingUtils.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/utils/EdcEndpointsMappingUtils.java new file mode 100644 index 0000000..8139b27 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/utils/EdcEndpointsMappingUtils.java @@ -0,0 +1,155 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.utils; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.valueaddedservice.dto.edc.EDRResponseDTO; + +import java.io.IOException; + +@Slf4j +public class EdcEndpointsMappingUtils { + + public EdcEndpointsMappingUtils() { + } + + private static final ObjectMapper objectMapper = createObjectMapper(); + + private static ObjectMapper createObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper; + } + + public static String extractLastNegotiatedTransferProcessId(String jsonResponse) { + String lastNegotiatedTransferProcessId = ""; + long latestExpirationDate = 0; + + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + if (rootNode.isArray()) { + for (JsonNode node : rootNode) { + if ("NEGOTIATED".equals(node.path("tx:edrState").asText()) && node.has("tx:expirationDate")) { + long currentExpirationDate = node.path("tx:expirationDate").asLong(); + // Check if this entry is more recent based on expirationDate + if (currentExpirationDate > latestExpirationDate) { + latestExpirationDate = currentExpirationDate; + if (node.has("transferProcessId")) { + lastNegotiatedTransferProcessId = node.path("transferProcessId").asText(); + } + } + } + } + } else { + log.info("Expected an array response for EDRs data but got a non-array response."); + } + } catch (IOException e) { + log.error("Error parsing JSON for the last negotiated transfer process ID: {}", e.getMessage()); + } + + if (lastNegotiatedTransferProcessId.isEmpty()) { + log.info("No negotiated transfer process ID found in the latest entry."); + } + return lastNegotiatedTransferProcessId; + } + + public static String extractNegotiationIdFromInitialRequest(String jsonResponse) { + ObjectMapper objectMapper = new ObjectMapper(); + String negotiationId = ""; + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + negotiationId = rootNode.get("@id").asText(); + } catch (IOException e) { + log.error("Error parsing negotiation ID from JSON: {}", e.getMessage()); + throw new RuntimeException("Error extracting negotiation ID"); + } + isEmpty(negotiationId,"extractNegotiationIdFromInitialRequest",jsonResponse); + return negotiationId; + } + + public static String extractContractAgreementId(String jsonResponse) { + ObjectMapper objectMapper = new ObjectMapper(); + String contractAgreementId = ""; + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + contractAgreementId = rootNode.path("contractAgreementId").asText(""); + } catch (IOException e) { + log.error("Error parsing contract agreement ID from JSON: {}", e.getMessage()); + throw new RuntimeException("Error extracting contract agreement ID"); + } + isEmpty(contractAgreementId,"extractContractAgreementId",jsonResponse); + return contractAgreementId; + } + + public static String extractTransferProcessId(String jsonResponse) { + ObjectMapper objectMapper = new ObjectMapper(); + String transferProcessId = ""; + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + if (rootNode.isArray() && !rootNode.isEmpty()) { + // Assuming you want the transferProcessId from the first object in the array + JsonNode firstItem = rootNode.get(0); + if (firstItem != null && firstItem.has("transferProcessId")) { + transferProcessId = firstItem.get("transferProcessId").asText(""); + } + } + } catch (IOException e) { + log.error("Error parsing transfer process ID from JSON: {}", e.getMessage()); + throw new RuntimeException("Error extracting transfer process ID"); + } + + isEmpty(transferProcessId,"extractTransferProcessId",jsonResponse); + return transferProcessId; + } + + + public static EDRResponseDTO getAuthCodeAndEndpoint(String jsonResponse) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + EDRResponseDTO response = new EDRResponseDTO(); + if (rootNode.has("authCode")) { + response.setAuthCode(rootNode.get("authCode").asText()); + } + if (rootNode.has("endpoint")) { + response.setEndpoint(rootNode.get("endpoint").asText()); + } + return response; + } catch (IOException e) { + log.error("Error parsing JSON: {}", e.getMessage()); + throw new RuntimeException("Error Getting authCode or Endpoint"); + } + + } + + private static void isEmpty(String extractMessage, String operation, String json){ + if(extractMessage.isEmpty()){ + log.error("Error Extracting from Operation: {}", operation); + log.error("Error Extracting from JSON: {}", json); + throw new RuntimeException(String.format("Error in operation: %s", operation)); + } + log.info("Found {} for Operation {}",extractMessage,operation); + } + +} + diff --git a/src/main/java/org/eclipse/tractusx/valueaddedservice/web/rest/NegotiationController.java b/src/main/java/org/eclipse/tractusx/valueaddedservice/web/rest/NegotiationController.java new file mode 100644 index 0000000..bfbfd79 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/valueaddedservice/web/rest/NegotiationController.java @@ -0,0 +1,80 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 BMW Group AG + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.valueaddedservice.web.rest; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.valueaddedservice.dto.edc.CatalogItemDTO; +import org.eclipse.tractusx.valueaddedservice.dto.edc.NegotiationRequestDTO; +import org.eclipse.tractusx.valueaddedservice.dto.edc.NegotiationResponseDTO; +import org.eclipse.tractusx.valueaddedservice.service.logic.EdcLogicService; +import org.eclipse.tractusx.valueaddedservice.service.logic.NegotiationServiceLogic; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/negotiation") +@Tag(name = "Negotiation Controller") +@SecurityRequirement(name = "bearerAuth") +@SecurityRequirement(name = "open_id_scheme") +@Slf4j +public class NegotiationController { + + @Autowired + private EdcLogicService edcLogicService; + + @Autowired + private NegotiationServiceLogic negotiationService; + + @Operation(summary = "Retrieves catalog items available for negotiation", + responses = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved catalog items", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = CatalogItemDTO.class))), + @ApiResponse(responseCode = "401", description = "Authentication Required"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @GetMapping("/queryCatalog") + public ResponseEntity> queryCatalog() { + List catalogItems = edcLogicService.queryCatalog(); + return ResponseEntity.ok(catalogItems); + } + + @Operation(summary = "Triggers negotiation with selected items", + responses = { + @ApiResponse(responseCode = "200", description = "Negotiation initiated successfully", + content = @Content(mediaType = "text/plain")), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "401", description = "Authentication Required"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @PostMapping("/triggerNegotiation") + public ResponseEntity> triggerNegotiation(@RequestBody List negotiationItems) { + return ResponseEntity.ok().body(negotiationService.triggerNegotiation(negotiationItems)); + } +} \ No newline at end of file diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index dc88346..d2635ae 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -108,7 +108,6 @@ security: logging: level: root: INFO - org.eclipse.tractusx.valueaddedservice: DEBUG application: name: default @@ -124,6 +123,9 @@ application: policyBpn: 'BPN000' apiKey: "" edc: + providers: + - BPDMPool + - BPDMGate enabled: false