diff --git a/backend/pom.xml b/backend/pom.xml index 695aab26..e79e5871 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -1,9 +1,9 @@ - - org.hibernate.validator - hibernate-validator - ${hibernate-validator.version} - - - - org.modelmapper - modelmapper - 3.2.0 - - + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.2 + + + org.eclipse.tractusx.puris + puris-backend + 1.0.0 + puris-backend + PURIS Backend + + 17 + 2.3.0 + 8.0.1.Final + 2.2 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.session + spring-session-core + + + org.hsqldb + hsqldb + 2.7.1 + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.postgresql + postgresql + 42.7.2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + org.modelmapper + modelmapper + 3.2.0 + - - - dash-licenses - https://repo.eclipse.org/content/repositories/dash-licenses - - + - - - - org.eclipse.dash - license-tool-plugin - 1.0.2 - - automotive.tractusx - - DEPENDENCIES - - test - - - - license-check - - license-check - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - org.hsqldb - hsqldb - - - - - - com.mycila - license-maven-plugin - 4.2 - - - -
scripts/license/header.txt
- - **/README - src/test/resources/** - src/main/resources/** - -
-
-
-
-
- - - - ${project.basedir}/src/main/resources - - application.properties - - BOOT-INF/classes/ - - - - ${project.basedir}/ - - README.md - LICENSE - NOTICE.md - DEPENDENCIES - SECURITY.md - - META-INF - - -
+ + + dash-licenses + https://repo.eclipse.org/content/repositories/dash-licenses + + + + + + + org.eclipse.dash + license-tool-plugin + 1.0.2 + + automotive.tractusx + + DEPENDENCIES + + test + + + + license-check + + license-check + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + org.hsqldb + hsqldb + + + + + + com.mycila + license-maven-plugin + 4.2 + + + +
scripts/license/header.txt
+ + **/README + src/test/resources/** + src/main/resources/** + +
+
+
+
+
+ + + + ${project.basedir}/src/main/resources + + application.properties + + BOOT-INF/classes/ + + + + ${project.basedir}/ + + README.md + LICENSE + NOTICE.md + DEPENDENCIES + SECURITY.md + + META-INF + + +
diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EdcController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EdcController.java index e4baf04d..0d929c0e 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EdcController.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EdcController.java @@ -59,16 +59,17 @@ public class EdcController { /** * Get the catalog from an EDC. * - * @param dspUrl url of the edc to get catalog from. + * @param dspUrl url of the edc to get catalog from. + * @param partnerBpnl bpnl of the partner to get the catalog from. * @return catalog of the requested edc. */ @GetMapping(CATALOG) - public ResponseEntity getCatalog(@RequestParam String dspUrl) { + public ResponseEntity getCatalog(@RequestParam String dspUrl, @RequestParam String partnerBpnl) { try { if (!PatternStore.URL_PATTERN.matcher(dspUrl).matches()) { return ResponseEntity.badRequest().build(); } - var catalogResponse = edcAdapter.getCatalogResponse(dspUrl); + var catalogResponse = edcAdapter.getCatalogResponse(dspUrl, partnerBpnl, null); if (catalogResponse != null && catalogResponse.isSuccessful()) { var responseString = catalogResponse.body().string(); catalogResponse.body().close(); @@ -175,12 +176,12 @@ public ResponseEntity getTransfers() { // in a transfer. Because we want to show the other party's // BPNL in the frontend in any case, we retrieve the BPNL via // the contractAgreement and insert it into the JSON data. - String myRole = item.get("edc:type").asText(); + String myRole = item.get("type").asText(); if ("PROVIDER".equals(myRole)) { - String contractId = item.get("edc:contractId").asText(); + String contractId = item.get("contractId").asText(); var contractObject = objectMapper.readTree(edcAdapter.getContractAgreement(contractId)); - String partnerBpnl = contractObject.get("edc:consumerId").asText(); - ((ObjectNode) item).put("edc:connectorId", partnerBpnl); + String partnerBpnl = contractObject.get("consumerId").asText(); + ((ObjectNode) item).put("connectorId", partnerBpnl); } } return ResponseEntity.ok(responseObject); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EndpointDataReferenceReceiver.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EndpointDataReferenceReceiver.java index 5f8a59ec..20bd59e6 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EndpointDataReferenceReceiver.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/controller/EndpointDataReferenceReceiver.java @@ -27,9 +27,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.puris.backend.common.util.PatternStore; import org.eclipse.tractusx.puris.backend.common.edc.logic.dto.EdrDto; import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EndpointDataReferenceService; +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; @@ -57,7 +57,7 @@ public class EndpointDataReferenceReceiver { * This endpoint awaits incoming EDR Tokens from external * partners during a consumer pull transfer. * - * @param body + * @param body received from edc containing access information * @return Status code 200 if request body was found, otherwise 400 */ @PostMapping("/edrendpoint") diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java index 8650b7bf..d80ff8d5 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.SubmodelType; @@ -32,14 +33,13 @@ import org.eclipse.tractusx.puris.backend.masterdata.domain.model.MaterialPartnerRelation; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.regex.Pattern; /** @@ -52,16 +52,14 @@ public class EdcAdapterService { private static final OkHttpClient CLIENT = new OkHttpClient(); @Autowired private VariablesService variablesService; - private ObjectMapper objectMapper; + private final ObjectMapper objectMapper; @Autowired private EdcRequestBodyBuilder edcRequestBodyBuilder; - @Autowired - private EndpointDataReferenceService edrService; @Autowired private EdcContractMappingService edcContractMappingService; - private Pattern urlPattern = PatternStore.URL_PATTERN; + private final Pattern urlPattern = PatternStore.URL_PATTERN; public EdcAdapterService(ObjectMapper objectMapper) { this.objectMapper = objectMapper; @@ -73,14 +71,18 @@ public EdcAdapterService(ObjectMapper objectMapper) { * the returned Response object after using it. * * @param pathSegments The path segments + * @param queryParams The query parameters to use * @return The response * @throws IOException If the connection to your control plane fails */ - public Response sendGetRequest(List pathSegments) throws IOException { + public Response sendGetRequest(List pathSegments, Map queryParams) throws IOException { HttpUrl.Builder urlBuilder = HttpUrl.parse(variablesService.getEdcManagementUrl()).newBuilder(); for (var pathSegment : pathSegments) { urlBuilder.addPathSegment(pathSegment); } + for (Map.Entry entry : queryParams.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } var request = new Request.Builder() .get() .url(urlBuilder.build()) @@ -89,12 +91,25 @@ public Response sendGetRequest(List pathSegments) throws IOException { return CLIENT.newCall(request).execute(); } + /** + * Util method for issuing a GET request to the management api of your control plane. + * Any caller of this method has the responsibility to close + * the returned Response object after using it. + * + * @param pathSegments The path segments + * @return The response + * @throws IOException If the connection to your control plane fails + */ + public Response sendGetRequest(List pathSegments) throws IOException { + return sendGetRequest(pathSegments, new HashMap<>()); + } + /** * Util method for issuing a POST request to the management api of your control plane. * Any caller of this method has the responsibility to close * the returned Response object after using it. * - * @param requestBody The request body + * @param requestBody The request body, not null * @param pathSegments The path segments * @return The response from your control plane * @throws IOException If the connection to your control plane fails @@ -112,6 +127,7 @@ private Response sendPostRequest(JsonNode requestBody, List pathSegments .header("X-Api-Key", variablesService.getEdcApiKey()) .header("Content-Type", "application/json") .build(); + return CLIENT.newCall(request).execute(); } @@ -133,16 +149,19 @@ public boolean registerAssetsInitially() { variablesService.getItemStockSubmodelEndpoint(), SubmodelType.ITEM_STOCK.URN_SEMANTIC_ID ))); + result &= assetRegistration; log.info("Registration of Planned Production 2.0.0 submodel successful {}", (assetRegistration = registerSubmodelAsset( variablesService.getProductionSubmodelApiAssetId(), variablesService.getProductionSubmodelEndpoint(), SubmodelType.PRODUCTION.URN_SEMANTIC_ID ))); + result &= assetRegistration; log.info("Registration of Short Term Material Demand 1.0.0 submodel successful {}", (assetRegistration = registerSubmodelAsset( variablesService.getDemandSubmodelApiAssetId(), variablesService.getDemandSubmodelEndpoint(), SubmodelType.DEMAND.URN_SEMANTIC_ID ))); + result &= assetRegistration; log.info("Registration of Delivery Information 2.0.0 submodel successful {}", (assetRegistration = registerSubmodelAsset( variablesService.getDeliverySubmodelApiAssetId(), variablesService.getDeliverySubmodelEndpoint(), @@ -307,25 +326,30 @@ private boolean registerSubmodelAsset(String assetId, String endpoint, String se * Retrieve the response to an unfiltered catalog request from the partner * with the given dspUrl * - * @param dspUrl The dspUrl of your partner + * @param dspUrl The dspUrl of your partner + * @param partnerBpnl The bpnl of your partner + * @param filter Map of key (leftOperand) and values (rightOperand) to use as filterExpression with equal operand * @return The response containing the full catalog, if successful */ - public Response getCatalogResponse(String dspUrl) throws IOException { - return sendPostRequest(edcRequestBodyBuilder.buildBasicCatalogRequestBody(dspUrl, null), List.of("v2", "catalog", "request")); + public Response getCatalogResponse(String dspUrl, String partnerBpnl, Map filter) throws IOException { + return sendPostRequest(edcRequestBodyBuilder.buildBasicCatalogRequestBody(dspUrl, partnerBpnl, filter), List.of("v2", "catalog", "request")); } /** * Retrieve an (unfiltered) catalog from the partner with the * given dspUrl * - * @param dspUrl The dspUrl of your partner + * @param dspUrl The dspUrl of your partner + * @param partnerBpnl The bpnl of your partner + * @param filter Map of key (leftOperand) and values (rightOperand) to use as filterExpression with equal operand * @return The full catalog * @throws IOException If the connection to the partners control plane fails */ - public JsonNode getCatalog(String dspUrl) throws IOException { - try (var response = getCatalogResponse(dspUrl)) { - String stringData = response.body().string(); - return objectMapper.readTree(stringData); + public JsonNode getCatalog(String dspUrl, String partnerBpnl, Map filter) throws IOException { + try (var response = getCatalogResponse(dspUrl, partnerBpnl, filter)) { + JsonNode responseNode = objectMapper.readTree(response.body().string()); + log.debug("Got Catalog response {}", responseNode.toPrettyString()); + return responseNode; } } @@ -333,6 +357,8 @@ public JsonNode getCatalog(String dspUrl) throws IOException { /** * Helper method for contracting a certain asset as specified in the catalog item from * a specific Partner. + *

+ * Uses the dspUrl of the partner. * * @param partner The Partner to negotiate with * @param catalogItem An excerpt from a catalog. @@ -340,10 +366,27 @@ public JsonNode getCatalog(String dspUrl) throws IOException { * @throws IOException If the connection to the partners control plane fails */ private JsonNode initiateNegotiation(Partner partner, JsonNode catalogItem) throws IOException { - var requestBody = edcRequestBodyBuilder.buildAssetNegotiationBody(partner, catalogItem); - try (var response = sendPostRequest(requestBody, List.of("v2", "contractnegotiations"))) { - String responseString = response.body().string(); - return objectMapper.readTree(responseString); + return initiateNegotiation(partner, catalogItem, null); + } + + /** + * Helper method for contracting a certain asset as specified in the catalog item from + * a specific Partner. + * + * @param partner The Partner to negotiate with + * @param catalogItem An excerpt from a catalog. + * @param dspUrl The dspUrl if a specific (not from MAD Partner) needs to be used, if null, the partners edcUrl is taken + * @return The JSON response to your contract offer. + * @throws IOException If the connection to the partners control plane fails + */ + private JsonNode initiateNegotiation(Partner partner, JsonNode catalogItem, String dspUrl) throws IOException { + // use dspUrl as provided, if set - else use partner + dspUrl = dspUrl != null && !dspUrl.isEmpty() ? dspUrl : partner.getEdcUrl(); + var requestBody = edcRequestBodyBuilder.buildAssetNegotiationBody(partner, catalogItem, dspUrl); + try (Response response = sendPostRequest(requestBody, List.of("v2", "contractnegotiations"))) { + JsonNode responseNode = objectMapper.readTree(response.body().string()); + log.debug("Result from negotiation {}", responseNode.toPrettyString()); + return responseNode; } } @@ -389,7 +432,9 @@ public JsonNode initiateProxyPullTransfer(Partner partner, String contractId, St var body = edcRequestBodyBuilder.buildProxyPullRequestBody(partner, contractId, assetId, partnerEdcUrl); try (var response = sendPostRequest(body, List.of("v2", "transferprocesses"))) { String data = response.body().string(); - return objectMapper.readTree(data); + JsonNode result = objectMapper.readTree(data); + log.debug("Got response from Proxy pull transfer init: {}", result.toPrettyString()); + return result; } } @@ -422,6 +467,7 @@ public JsonNode getTransferState(String transferId) throws IOException { */ public Response getAllTransfers() throws IOException { var requestBody = edcRequestBodyBuilder.buildTransfersRequestBody(); + log.debug("GetAllTransfers Request: {}", requestBody.toPrettyString()); return sendPostRequest(requestBody, List.of("v2", "transferprocesses", "request")); } @@ -518,38 +564,38 @@ private JsonNode getSubmodelFromPartner(MaterialPartnerRelation mpr, SubmodelTyp } // Request EdrToken var transferResp = initiateProxyPullTransfer(partner, submodelContractId, assetId, partnerDspUrl); + log.debug("Transfer Request {}", transferResp.toPrettyString()); String transferId = transferResp.get("@id").asText(); - for (int i = 0; i < 100; i++) { - Thread.sleep(100); - transferResp = getTransferState(transferId); - if ("STARTED".equals(transferResp.get("edc:state").asText())) { - break; + // try proxy pull and terminate request + try { + for (int i = 0; i < 100; i++) { + Thread.sleep(100); + transferResp = getTransferState(transferId); + if ("STARTED".equals(transferResp.get("state").asText())) { + break; + } } - } - EdrDto edrDto = null; - // Await arrival of edr - for (int i = 0; i < 100; i++) { - Thread.sleep(100); - edrDto = edrService.findByTransferId(transferId); - if (edrDto != null) { - log.info("Received EDR data for " + assetId + " with " + partner.getEdcUrl()); - break; + EdrDto edrDto = getAndAwaitEdrDto(transferId); + log.info("Received EDR data for " + assetId + " with " + partner.getEdcUrl()); + if (edrDto == null) { + log.error("Failed to obtain EDR data for " + assetId + " with " + partner.getEdcUrl()); + return doSubmodelRequest(type, mpr, direction, --retries); } - } - if (edrDto == null) { - log.error("Failed to obtain EDR data for " + assetId + " with " + partner.getEdcUrl()); - return doSubmodelRequest(type, mpr, direction, --retries); - } - if (!submodelData.href().startsWith(edrDto.endpoint())) { - log.warn("Diverging URLs in ItemStock Submodel request"); - log.warn("href: " + submodelData.href()); - log.warn("Data plane base URL from EDR: " + edrDto.endpoint()); - } - try (var response = getProxyPullRequest(submodelData.href, edrDto.authKey(), edrDto.authCode(), new String[]{type.REPRESENTATION})) { - if (response.isSuccessful()) { - String responseString = response.body().string(); - failed = false; - return objectMapper.readTree(responseString); + if (!submodelData.href().startsWith(edrDto.endpoint())) { + log.warn("Diverging URLs in ItemStock Submodel request"); + log.warn("href: " + submodelData.href()); + log.warn("Data plane base URL from EDR: " + edrDto.endpoint()); + } + try (var response = getProxyPullRequest(submodelData.href, edrDto.authKey(), edrDto.authCode(), new String[]{type.REPRESENTATION})) { + if (response.isSuccessful()) { + String responseString = response.body().string(); + failed = false; + return objectMapper.readTree(responseString); + } + } + } finally { + if (transferId != null) { + terminateTransfer(transferId); } } } catch (Exception e) { @@ -563,6 +609,26 @@ private JsonNode getSubmodelFromPartner(MaterialPartnerRelation mpr, SubmodelTyp return getSubmodelFromPartner(mpr, type, direction, --retries); } + /** + * get the EDR via edr api and retry multiple times in case the EDR has not yet been available + * + * @param transferProcessId to get the EDR for, not null + * @return edr received, or null if not yet available + * @throws InterruptedException if thread was not able to sleep + */ + private @Nullable EdrDto getAndAwaitEdrDto(String transferProcessId) throws InterruptedException { + EdrDto edrDto = null; + // retry, if Data Space Protocol / Data Plane Provisioning communication needs time to prepare + for (int i = 0; i < 100; i++) { + edrDto = getEdrForTransferProcessId(transferProcessId); + if (edrDto != null) { + break; + } + Thread.sleep(100); + } + return edrDto; + } + public JsonNode doSubmodelRequest(SubmodelType type, MaterialPartnerRelation mpr, DirectionCharacteristic direction, int retries) { if (retries < 0) { return null; @@ -576,7 +642,13 @@ public JsonNode doSubmodelRequest(SubmodelType type, MaterialPartnerRelation mpr private boolean negotiateForPartnerDtr(Partner partner) { try { - var responseNode = getCatalog(partner.getEdcUrl()); + Map equalFilters = new HashMap<>(); + equalFilters.put(EdcRequestBodyBuilder.CX_COMMON_NAMESPACE + "version", "3.0"); + equalFilters.put( + "'" + EdcRequestBodyBuilder.DCT_NAMESPACE + "type'.'@id'", + EdcRequestBodyBuilder.CX_TAXO_NAMESPACE + "DigitalTwinRegistry" + ); + var responseNode = getCatalog(partner.getEdcUrl(), partner.getBpnl(), equalFilters); var catalogArray = responseNode.get("dcat:dataset"); // If there is exactly one asset, the catalogContent will be a JSON object. // In all other cases catalogContent will be a JSON array. @@ -584,36 +656,28 @@ private boolean negotiateForPartnerDtr(Partner partner) { if (catalogArray.isObject()) { catalogArray = objectMapper.createArrayNode().add(catalogArray); } - JsonNode targetCatalogEntry = null; - for (var entry : catalogArray) { - var dctTypeObject = entry.get("dct:type"); - if (dctTypeObject != null) { - if (("https://w3id.org/catenax/taxonomy#DigitalTwinRegistry").equals(dctTypeObject.get("@id").asText())) { - if ("3.0".equals(entry.get("https://w3id.org/catenax/ontology/common#version").asText())) { - if (targetCatalogEntry == null) { - targetCatalogEntry = entry; - } else { - log.warn("Ambiguous catalog entries found! \n" + catalogArray.toPrettyString()); - } - } - } - } + if (catalogArray.size() > 1) { + log.warn("Ambiguous catalog entries found! Will take the first\n" + catalogArray.toPrettyString()); + // potential constraint check in future } + JsonNode targetCatalogEntry = catalogArray.get(0); if (targetCatalogEntry == null) { log.error("Could not find asset for DigitalTwinRegistry at partner " + partner.getBpnl() + "'s catalog"); - log.warn("CATALOG CONTENT \n" + catalogArray.toPrettyString()); return false; } String assetId = targetCatalogEntry.get("@id").asText(); + log.debug("Found contract offer for asset {}", assetId); JsonNode negotiationResponse = initiateNegotiation(partner, targetCatalogEntry); String negotiationId = negotiationResponse.get("@id").asText(); + log.info("Started negotiation with id {}", negotiationId); // Await confirmation of contract and contractId String contractId = null; for (int i = 0; i < 100; i++) { Thread.sleep(100); var responseObject = getNegotiationState(negotiationId); - if ("FINALIZED".equals(responseObject.get("edc:state").asText())) { - contractId = responseObject.get("edc:contractAgreementId").asText(); + if ("FINALIZED".equals(responseObject.get("state").asText())) { + contractId = responseObject.get("contractAgreementId").asText(); + log.info("Contracted DTR with contractAgreementId {}", contractId); break; } } @@ -682,6 +746,17 @@ private SubmodelData fetchSubmodelData(MaterialPartnerRelation mpr, String seman return null; } + /** + * quries the dtr of a pratner for the given mpr / material and returns submodel descriptors + *

+ * Method assumes that the query at dtr only finds one shell (else take first entry) + * + * @param manufacturerPartId material number of the supplier party + * @param manufacturerId bpnl of the supplier party + * @param mpr containing the mapping between material and partner to lookup at dtr + * @param retries number of times to retry in case the shell could not (yet) been found + * @return array of submodelDescriptors of the found shell + */ private JsonNode getAasSubmodelDescriptors(String manufacturerPartId, String manufacturerId, MaterialPartnerRelation mpr, int retries) { if (retries < 0) { log.error("AasSubmodelDescriptors Request failed for " + manufacturerPartId + " and " + manufacturerId); @@ -703,85 +778,85 @@ private JsonNode getAasSubmodelDescriptors(String manufacturerPartId, String man } var transferResp = initiateProxyPullTransfer(partner, contractId, assetId); String transferId = transferResp.get("@id").asText(); - for (int i = 0; i < 100; i++) { - Thread.sleep(100); - transferResp = getTransferState(transferId); - if ("STARTED".equals(transferResp.get("edc:state").asText())) { - break; + try { + for (int i = 0; i < 100; i++) { + Thread.sleep(100); + transferResp = getTransferState(transferId); + if ("STARTED".equals(transferResp.get("state").asText())) { + break; + } } - } - EdrDto edrDto = null; - // Await arrival of edr - for (int i = 0; i < 100; i++) { - edrDto = edrService.findByTransferId(transferId); - if (edrDto != null) { + EdrDto edrDto = getAndAwaitEdrDto(transferId); + if (edrDto == null) { + log.error("Failed to obtain EDR data for " + assetId + " with " + partner.getEdcUrl()); + return getAasSubmodelDescriptors(manufacturerPartId, manufacturerId, mpr, --retries); + } else { log.info("Received EDR data for " + assetId + " with " + partner.getEdcUrl()); - break; } - Thread.sleep(100); - } - if (edrDto == null) { - log.error("Failed to obtain EDR data for " + assetId + " with " + partner.getEdcUrl()); - return getAasSubmodelDescriptors(manufacturerPartId, manufacturerId, mpr, --retries); - } - HttpUrl.Builder urlBuilder = HttpUrl.parse(edrDto.endpoint()).newBuilder() - .addPathSegment("api") - .addPathSegment("v3") - .addPathSegment("lookup") - .addPathSegment("shells"); - String query = "{\"name\":\"manufacturerPartId\",\"value\":\"" + manufacturerPartId + "\"}"; - query += ",{\"name\":\"digitalTwinType\",\"value\":\"PartType\"}"; - query += ",{\"name\":\"manufacturerId\",\"value\":\"" + manufacturerId + "\"}"; - String encodedQuery = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8)); - urlBuilder.addQueryParameter("assetIds", encodedQuery); - var request = new Request.Builder() - .get() - .header(edrDto.authKey(), edrDto.authCode()) - .url(urlBuilder.build()) - .build(); - try (var response = CLIENT.newCall(request).execute()) { - var bodyString = response.body().string(); - var jsonResponse = objectMapper.readTree(bodyString); - var resultArray = jsonResponse.get("result"); - if (resultArray != null && resultArray.isArray() && !resultArray.isEmpty()) { - if (resultArray.size() > 1) { - log.warn("Found more than one result for query " + query); - log.info(resultArray.toPrettyString()); - } - String aasId = resultArray.get(0).asText(); - urlBuilder = HttpUrl.parse(edrDto.endpoint()).newBuilder() - .addPathSegment("api") - .addPathSegment("v3") - .addPathSegment("shell-descriptors"); - String base64AasId = Base64.getEncoder().encodeToString(aasId.getBytes(StandardCharsets.UTF_8)); - urlBuilder.addPathSegment(base64AasId); - request = new Request.Builder() - .get() - .header(edrDto.authKey(), edrDto.authCode()) - .url(urlBuilder.build()) - .build(); - try (var response2 = CLIENT.newCall(request).execute()) { - var body2String = response2.body().string(); - var aasJson = objectMapper.readTree(body2String); - var submodelDescriptors = aasJson.get("submodelDescriptors"); - if (submodelDescriptors != null) { - failed = false; - return submodelDescriptors; - } else { - log.warn("No SubmodelDescriptors found in DTR shell-descriptors response:\n" + aasJson.toPrettyString()); + HttpUrl.Builder urlBuilder = HttpUrl.parse(edrDto.endpoint()).newBuilder() + .addPathSegment("api") + .addPathSegment("v3") + .addPathSegment("lookup") + .addPathSegment("shells"); + String query = "{\"name\":\"manufacturerPartId\",\"value\":\"" + manufacturerPartId + "\"}"; + query += ",{\"name\":\"digitalTwinType\",\"value\":\"PartType\"}"; + query += ",{\"name\":\"manufacturerId\",\"value\":\"" + manufacturerId + "\"}"; + String encodedQuery = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8)); + urlBuilder.addQueryParameter("assetIds", encodedQuery); + var request = new Request.Builder() + .get() + .header(edrDto.authKey(), edrDto.authCode()) + .url(urlBuilder.build()) + .build(); + try (var response = CLIENT.newCall(request).execute()) { + var bodyString = response.body().string(); + var jsonResponse = objectMapper.readTree(bodyString); + var resultArray = jsonResponse.get("result"); + if (resultArray != null && resultArray.isArray() && !resultArray.isEmpty()) { + if (resultArray.size() > 1) { + log.warn("Found more than one result for query " + query); + log.info(resultArray.toPrettyString()); } - } - } else { - if (resultArray != null) { - if (resultArray.isArray() && resultArray.isEmpty()) { - log.warn("Empty Result array received"); - } else { - log.warn("Unexpected Response for DTR lookup with query " + query + "\n" + resultArray.toPrettyString()); + String aasId = resultArray.get(0).asText(); + urlBuilder = HttpUrl.parse(edrDto.endpoint()).newBuilder() + .addPathSegment("api") + .addPathSegment("v3") + .addPathSegment("shell-descriptors"); + String base64AasId = Base64.getEncoder().encodeToString(aasId.getBytes(StandardCharsets.UTF_8)); + urlBuilder.addPathSegment(base64AasId); + request = new Request.Builder() + .get() + .header(edrDto.authKey(), edrDto.authCode()) + .url(urlBuilder.build()) + .build(); + try (var response2 = CLIENT.newCall(request).execute()) { + var body2String = response2.body().string(); + var aasJson = objectMapper.readTree(body2String); + var submodelDescriptors = aasJson.get("submodelDescriptors"); + if (submodelDescriptors != null) { + failed = false; + return submodelDescriptors; + } else { + log.warn("No SubmodelDescriptors found in DTR shell-descriptors response:\n" + aasJson.toPrettyString()); + } } } else { - log.warn("No Result Array received in DTR lookup response: \n" + jsonResponse.toPrettyString()); + if (resultArray != null) { + if (resultArray.isArray() && resultArray.isEmpty()) { + log.warn("Empty Result array received"); + } else { + log.warn("Unexpected Response for DTR lookup with query " + query + "\n" + resultArray.toPrettyString()); + } + } else { + log.warn("No Result Array received in DTR lookup response: \n" + jsonResponse.toPrettyString()); + } } } + + } finally { + if (transferId != null) { + terminateTransfer(transferId); + } } } catch (Exception e) { log.error("Error in AasSubmodelDescriptor Request for " + mpr + " and manufacturerPartId " + manufacturerPartId, e); @@ -795,6 +870,68 @@ private JsonNode getAasSubmodelDescriptors(String manufacturerPartId, String man return getAasSubmodelDescriptors(manufacturerPartId, manufacturerId, mpr, --retries); } + /** + * Requests an EDR for the communication from edc + *

+ * The edc already handles the expiry as configured in the provider data plane and refreshs the token before + * answering. + * + * @param transferProcessId to get the EDR for + * @return unpersisted EdrDto. + */ + private EdrDto getEdrForTransferProcessId(String transferProcessId) { + + try (Response response = sendGetRequest( + List.of("v2", "edrs", transferProcessId, "dataaddress"), + Map.of("auto_refresh", "true")) + ) { + ObjectNode responseObject = (ObjectNode) objectMapper.readTree(response.body().string()); + + String dataPlaneEndpoint = responseObject.get("endpoint").asText(); + String authToken = responseObject.get("authorization").asText(); + + EdrDto edr = new EdrDto("Authorization", authToken, dataPlaneEndpoint); + log.debug("Requested EDR successfully: {}", edr); + + return edr; + + } catch (IOException e) { + log.error("EDR token for transfer process with ID {} could not be obtained", transferProcessId); + } + + return null; + } + + /** + * terminate the transfer with reason "Transfer done." + *

+ * Edr in {@link EndpointDataReferenceService} is not removed because it is removed automatically by a job after + * time period x. + * + * @param transferProcessId to terminate + */ + private void terminateTransfer(String transferProcessId) { + + JsonNode body = edcRequestBodyBuilder.buildTransferProcessTerminationBody("Transfer done."); + + try (Response response = sendPostRequest(body, List.of("v2", "transferprocesses", transferProcessId, "terminate"))) { + + JsonNode resultNode = objectMapper.readTree(response.body().string()); + if (!response.isSuccessful()) { + log.error( + "Transfer process with id {} could not be termianted; status code {}, reason: {}", + transferProcessId, + response.code(), + resultNode.get("message").asText("MESSAGE NOT FOUND") + ); + } else { + log.info("Terminated transfer process with id {}.", transferProcessId); + } + } catch (IOException e) { + log.error("Error while trying to terminate transfer: ", e); + } + } + /** * Tries to negotiate for a partner's Submodel API.

* If successful, the contractId as well as the assetId @@ -819,7 +956,14 @@ private boolean negotiateForSubmodel(MaterialPartnerRelation mpr, SubmodelType t case PART_TYPE_INFORMATION -> fetchPartTypeSubmodelData(mpr); }; try { - var responseNode = getCatalog(submodelData.dspUrl()); + Map equalFilters = new HashMap<>(); + equalFilters.put(EdcRequestBodyBuilder.CX_COMMON_NAMESPACE + "version", "3.0"); + equalFilters.put( + "'" + EdcRequestBodyBuilder.DCT_NAMESPACE + "type'.'@id'", + EdcRequestBodyBuilder.CX_TAXO_NAMESPACE + "Submodel" + ); + equalFilters.put("'" + EdcRequestBodyBuilder.AAS_SEMANTICS_NAMESPACE + "semanticId'.'@id'", type.URN_SEMANTIC_ID); + var responseNode = getCatalog(submodelData.dspUrl(), partner.getBpnl(), equalFilters); var catalogArray = responseNode.get("dcat:dataset"); // If there is exactly one asset, the catalogContent will be a JSON object. // In all other cases catalogContent will be a JSON array. @@ -828,45 +972,30 @@ private boolean negotiateForSubmodel(MaterialPartnerRelation mpr, SubmodelType t catalogArray = objectMapper.createArrayNode().add(catalogArray); } JsonNode targetCatalogEntry = null; - for (var entry : catalogArray) { - var semanticId = entry.get("aas-semantics:semanticId"); - if (semanticId == null) { - continue; - } - String idString = semanticId.get("@id").asText(); - if (idString == null) { - continue; - } - if (type.URN_SEMANTIC_ID.equals(idString) && submodelData.assetId.equals(entry.get("edc:id").asText())) { - if (targetCatalogEntry == null) { - if (testContractPolicyConstraints(entry)) { - targetCatalogEntry = entry; - } else { - log.error("Contract Negotiation for " + type + " Submodel asset with partner " + partner.getBpnl() + " has " + - "been aborted. This partner's contract policy does not match the policy " + - "supported by this application. \n Supported Policy: " + variablesService.getPurisFrameworkAgreement() + - "\n Received offer from Partner: \n" + entry.toPrettyString()); - break; - } - } else { - log.warn("Ambiguous catalog entries found! \n" + catalogArray.toPrettyString()); + if (!catalogArray.isEmpty()) { + log.debug("Ambiguous catalog entries found! Will take the first with supported policy \n" + catalogArray.toPrettyString()); + for (JsonNode entry : catalogArray) { + if (testContractPolicyConstraints(entry)) { + targetCatalogEntry = entry; + break; } } } + if (targetCatalogEntry == null) { log.error("Could not find asset for " + type + " Submodel at partner " + partner.getBpnl() + "'s catalog"); log.warn("CATALOG CONTENT \n" + catalogArray.toPrettyString()); return false; } - JsonNode negotiationResponse = initiateNegotiation(partner, targetCatalogEntry); + JsonNode negotiationResponse = initiateNegotiation(partner, targetCatalogEntry, submodelData.dspUrl()); String negotiationId = negotiationResponse.get("@id").asText(); // Await confirmation of contract and contractId String contractId = null; for (int i = 0; i < 100; i++) { Thread.sleep(100); var responseObject = getNegotiationState(negotiationId); - if ("FINALIZED".equals(responseObject.get("edc:state").asText())) { - contractId = responseObject.get("edc:contractAgreementId").asText(); + if ("FINALIZED".equals(responseObject.get("state").asText())) { + contractId = responseObject.get("contractAgreementId").asText(); break; } } @@ -897,7 +1026,7 @@ private boolean negotiateForSubmodel(MaterialPartnerRelation mpr, SubmodelType t * @return the partner's CXid for that material */ public String getCxIdFromPartTypeInformation(MaterialPartnerRelation mpr) { - var data = getSubmodelFromPartner(mpr, SubmodelType.PART_TYPE_INFORMATION, null, 1); + var data = getSubmodelFromPartner(mpr, SubmodelType.PART_TYPE_INFORMATION, null, 1); return data.get("catenaXId").asText(); } @@ -909,22 +1038,92 @@ public String getCxIdFromPartTypeInformation(MaterialPartnerRelation mpr) { * @return true, if the policy matches yours, otherwise false */ private boolean testContractPolicyConstraints(JsonNode catalogEntry) { + log.debug("Testing constraints in the following catalogEntry: {}", catalogEntry.toPrettyString()); var constraint = Optional.ofNullable(catalogEntry.get("odrl:hasPolicy")) .map(policy -> policy.get("odrl:permission")) - .map(permission -> permission.get("odrl:constraint")); - if (constraint.isEmpty()) return false; + .map(permission -> permission.get("odrl:constraint")) + .map(con -> con.get("odrl:and")); + if (constraint.isEmpty()) { + log.debug("Constraint mismatch: we expect to have a constraint in permission node."); + return false; + } + boolean result = true; + + if (constraint.get().isArray() && constraint.get().size() == 2) { + Optional frameworkAgreementConstraint = Optional.empty(); + Optional purposeConstraint = Optional.empty(); + + for (JsonNode con : constraint.get()) { // Iterate over array elements and find the nodes + JsonNode leftOperandNode = con.get("odrl:leftOperand"); + if (leftOperandNode != null && (EdcRequestBodyBuilder.CX_POLICY_NAMESPACE + "FrameworkAgreement").equals(leftOperandNode.asText())) { + frameworkAgreementConstraint = Optional.of(con); + } + if (leftOperandNode != null && (EdcRequestBodyBuilder.CX_POLICY_NAMESPACE + "UsagePurpose").equals(leftOperandNode.asText())) { + purposeConstraint = Optional.of(con); + } + } + + if (frameworkAgreementConstraint.isPresent()) { + result = result && testSingleConstraint( + frameworkAgreementConstraint, + EdcRequestBodyBuilder.CX_POLICY_NAMESPACE + "FrameworkAgreement", + "odrl:eq", + variablesService.getPurisFrameworkAgreementWithVersion() + ); + } else { + log.debug("FrameworkAgreement Policy not found."); + } + + if (purposeConstraint.isPresent()) { + result = result && testSingleConstraint( + purposeConstraint, + EdcRequestBodyBuilder.CX_POLICY_NAMESPACE + "UsagePurpose", + "odrl:eq", + variablesService.getPurisPuposeWithVersion() + ); + } else { + log.debug("Usage Purpose Policy not found."); + } + } else { + log.info( + "2 Constraints (Framework Agreement, Purpose) are expected but got {} constraints.", + constraint.get().size() + ); + return false; + } + + return result; + } - var leftOperand = constraint.map(cons -> cons.get("odrl:leftOperand")) - .filter(operand -> variablesService.getPurisFrameworkAgreement().equals(operand.asText())); + private boolean testSingleConstraint(Optional constraintToTest, String targetLeftOperand, String targetOperator, String targetRightOperand) { - var operator = constraint.map(cons -> cons.get("odrl:operator")) - .map(op -> op.get("@id")) - .filter(operand -> "odrl:eq".equals(operand.asText())); + if (constraintToTest.isEmpty()) return false; - var rightOperand = constraint.map(cons -> cons.get("odrl:rightOperand")) - .filter(operand -> "active".equals(operand.asText())); + JsonNode con = constraintToTest.get(); + + JsonNode leftOperandNode = con.get("odrl:leftOperand"); + if (leftOperandNode == null || !targetLeftOperand.equals(leftOperandNode.asText())) { + String leftOperand = leftOperandNode == null ? "null" : leftOperandNode.asText(); + log.debug("Left operand '{}' does not equal expected value '{}'.", leftOperand, targetLeftOperand); + return false; + } + + JsonNode operatorNode = con.get("odrl:operator"); + operatorNode = operatorNode == null ? null : operatorNode.get("@id"); + if (operatorNode == null || !targetOperator.equals(operatorNode.asText())) { + String operator = operatorNode == null ? "null" : operatorNode.asText(); + log.debug("Operator '{}' does not equal expected value '{}'.", operator, targetOperator); + return false; + } + + JsonNode rightOperandNode = con.get("odrl:rightOperand"); + if (rightOperandNode == null || !targetRightOperand.equals(rightOperandNode.asText())) { + String rightOperand = rightOperandNode == null ? "null" : rightOperandNode.asText(); + log.debug("Right operand '{}' odes not equal expected value '{}'.", rightOperand, targetRightOperand); + return false; + } - if (leftOperand.isEmpty() || operator.isEmpty() || rightOperand.isEmpty()) return false; + log.info("Contract Offer constraints can be fulfilled by PURIS FOSS application (passed)."); return true; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EndpointDataReferenceService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EndpointDataReferenceService.java index ff74ed94..f3ac2a99 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EndpointDataReferenceService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EndpointDataReferenceService.java @@ -21,8 +21,8 @@ package org.eclipse.tractusx.puris.backend.common.edc.logic.service; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.puris.backend.common.util.VariablesService; import org.eclipse.tractusx.puris.backend.common.edc.logic.dto.EdrDto; +import org.eclipse.tractusx.puris.backend.common.util.VariablesService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -30,19 +30,20 @@ import java.util.concurrent.ExecutorService; /** - * This class stores authCodes which are generated in the course of + * This class stores authCodes which are generated in the course of * the contracting for the request or response api. Since authCodes - * expire after a very short period, all stored items will be deleted - * after a number of minutes specified in the parameter own.authcodes.deletiontimer. + * expire after a very short period, all stored items will be deleted + * after a number of minutes specified in the parameter own.authcodes.deletiontimer. */ @Service @Slf4j public class EndpointDataReferenceService { - /** AuthCodes expire after a very short period and the data is quite voluminous, - * therefore it's not really useful to persist them in the database. - * The key is the transferId, the value is the authCode - */ + /** + * AuthCodes expire after a very short period and the data is quite voluminous, + * therefore it's not really useful to persist them in the database. + * The key is the transferId, the value is the authCode + */ final private HashMap nonpersistantRepository = new HashMap<>(); @Autowired private VariablesService variablesService; @@ -50,17 +51,18 @@ public class EndpointDataReferenceService { private ExecutorService executorService; /** - * Stores transferId and authCode as a key/value-pair. + * Stores transferId and authCode as a key/value-pair. * Please note that any data will only be stored for a period of 5 - * minutes. - * @param transferId - * @param edr_Dto + * minutes. + * + * @param transferId to store the edr associated to + * @param edr_Dto to store providing the access */ public void save(String transferId, EdrDto edr_Dto) { nonpersistantRepository.put(transferId, edr_Dto); final long timer = variablesService.getEdrTokenDeletionTimer() * 60 * 1000; // Start timer for deletion - executorService.submit(()-> { + executorService.submit(() -> { try { Thread.sleep(timer); } catch (InterruptedException e) { @@ -72,12 +74,11 @@ public void save(String transferId, EdrDto edr_Dto) { } /** - * * @param transferId The key under which the Dto is supposed to be stored * @return the Dto or null, if there is no authCode recorded under the given parameter */ public EdrDto findByTransferId(String transferId) { return nonpersistantRepository.get(transferId); } - + } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/util/EdcRequestBodyBuilder.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/util/EdcRequestBodyBuilder.java index b516d6b6..59f8f7ca 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/util/EdcRequestBodyBuilder.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/util/EdcRequestBodyBuilder.java @@ -23,15 +23,16 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; - import lombok.extern.slf4j.Slf4j; - import org.eclipse.tractusx.puris.backend.common.util.VariablesService; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -45,13 +46,22 @@ public class EdcRequestBodyBuilder { private VariablesService variablesService; @Autowired private ObjectMapper MAPPER; - private final String EDC_NAMESPACE = "https://w3id.org/edc/v0.0.1/ns/"; - private final String VOCAB_KEY = "@vocab"; - private final String ODRL_NAMESPACE = "http://www.w3.org/ns/odrl/2/"; - private final String CX_TAXO_NAMESPACE = "https://w3id.org/catenax/taxonomy#"; - private final String CX_COMMON_NAMESPACE = "https://w3id.org/catenax/ontology/common#"; - private final String DCT_NAMESPACE = "https://purl.org/dc/terms/"; - private final String CONTRACT_POLICY_ID = "Contract_Policy"; + public static final String EDC_NAMESPACE = "https://w3id.org/edc/v0.0.1/ns/"; + public static final String VOCAB_KEY = "@vocab"; + public static final String ODRL_NAMESPACE = "http://www.w3.org/ns/odrl/2/"; + public static final String ODRL_REMOTE_CONTEXT = "http://www.w3.org/ns/odrl.jsonld"; + public static final String CX_TAXO_NAMESPACE = "https://w3id.org/catenax/taxonomy#"; + public static final String CX_COMMON_NAMESPACE = "https://w3id.org/catenax/ontology/common#"; + public static final String CX_POLICY_NAMESPACE = "https://w3id.org/catenax/policy/"; + public static final String DCT_NAMESPACE = "https://purl.org/dc/terms/"; + public static final String AAS_SEMANTICS_NAMESPACE = "https://admin-shell.io/aas/3/0/HasSemantics/"; + public static final String CONTRACT_POLICY_ID = "Contract_Policy"; + + /** + * helper class to encapsulate PolicyConstraint + **/ + private record PolicyConstraint(String leftOperand, String operator, String rightOperand) { + } /** * Creates a request body for requesting a catalog in DSP protocol. @@ -60,117 +70,144 @@ public class EdcRequestBodyBuilder { * for the filter criteria programmatically. * * @param counterPartyDspUrl The protocol url of the other party + * @param counterPartyBpnl The bpnl of the other party * @param filter Key-value-pairs, may be empty or null * @return The request body */ - public ObjectNode buildBasicCatalogRequestBody(String counterPartyDspUrl, Map filter) { + public ObjectNode buildBasicCatalogRequestBody(String counterPartyDspUrl, String counterPartyBpnl, Map filter) { var objectNode = getEdcContextObject(); objectNode.put("protocol", "dataspace-protocol-http"); objectNode.put("@type", "CatalogRequest"); objectNode.put("counterPartyAddress", counterPartyDspUrl); + objectNode.put("counterPartyId", counterPartyBpnl); if (filter != null && !filter.isEmpty()) { - var querySpecNode = MAPPER.createObjectNode(); - objectNode.set("querySpec", querySpecNode); + ObjectNode querySpecObject = MAPPER.createObjectNode(); + ArrayNode filterExpressionsArray = MAPPER.createArrayNode(); + querySpecObject.set("filterExpression", filterExpressionsArray); for (var entry : filter.entrySet()) { - querySpecNode.put(entry.getKey(), entry.getValue()); + ObjectNode filterExpressionObject = MAPPER.createObjectNode(); + filterExpressionObject.put("operandLeft", entry.getKey()); + filterExpressionObject.put("operator", "="); + filterExpressionObject.put("operandRight", entry.getValue()); + filterExpressionsArray.add(filterExpressionObject); } + objectNode.set("querySpec", querySpecObject); } + log.debug("Built Catalog Request: \n" + objectNode.toPrettyString()); return objectNode; } /** - * Creates a request body that demands all of the following conditions: - * 1. The BPNL of the requesting connector is equal to the BPNL of the partner - * 2. There's a valid CX membership credential present + * create a policy for given constraints * - * @param partner the partner to create the policy for - * @return the request body as a {@link JsonNode} + * @param policyId to use as for identification (must be unique) + * @param constraints list of constraints that are assembled via odrl:and (note: also if just one is given, and will be put) + * @param policyProfile profile to use for odrl:policy, may be null (should only be used for contract policies) + * @return body to use for policy request */ - public JsonNode buildBpnAndMembershipRestrictedPolicy(Partner partner) { - var body = MAPPER.createObjectNode(); - var context = MAPPER.createObjectNode(); - context.put("odrl", ODRL_NAMESPACE); - body.set("@context", context); + private JsonNode buildPolicy(String policyId, List constraints, String policyProfile) { + ObjectNode body = getPolicyContextObject(); body.put("@type", "PolicyDefinitionRequestDto"); - body.put("@id", getBpnPolicyId(partner)); + body.put("@id", policyId); var policy = MAPPER.createObjectNode(); - body.set("policy", policy); - policy.put("@type", "Policy"); + body.set("edc:policy", policy); + policy.put("@type", "Set"); + + if (policyProfile != null && !policyProfile.isEmpty()) { + policy.put("profile", policyProfile); + } var permissionsArray = MAPPER.createArrayNode(); - policy.set("odrl:permission", permissionsArray); + policy.set("permission", permissionsArray); var permissionsObject = MAPPER.createObjectNode(); permissionsArray.add(permissionsObject); - permissionsObject.put("odrl:action", "USE"); + permissionsObject.put("action", "use"); var constraintObject = MAPPER.createObjectNode(); - permissionsObject.set("odrl:constraint", constraintObject); + permissionsObject.set("constraint", constraintObject); constraintObject.put("@type", "LogicalConstraint"); var andArray = MAPPER.createArrayNode(); - constraintObject.set("odrl:and", andArray); + constraintObject.set("and", andArray); - var bpnlpConstraint = MAPPER.createObjectNode(); - andArray.add(bpnlpConstraint); - bpnlpConstraint.put("@type", "Constraint"); - bpnlpConstraint.put("odrl:leftOperand", "BusinessPartnerNumber"); + for (PolicyConstraint policyConstraint : constraints) { + ObjectNode constraint = MAPPER.createObjectNode(); + constraint.put("@type", "LogicalConstraint"); + constraint.put("leftOperand", policyConstraint.leftOperand); - var bpnlOperator = MAPPER.createObjectNode(); - bpnlpConstraint.set("odrl:operator", bpnlOperator); - bpnlOperator.put("@id", "odrl:eq"); + constraint.put("operator", policyConstraint.operator); - bpnlpConstraint.put("odrl:rightOperand", partner.getBpnl()); + constraint.put("rightOperand", policyConstraint.rightOperand); + andArray.add(constraint); + } - var membershipConstraint = MAPPER.createObjectNode(); - andArray.add(membershipConstraint); - membershipConstraint.put("@type", "Constraint"); - membershipConstraint.put("odrl:leftOperand", "Membership"); + return body; + } + + /** + * Creates a request body that demands all of the following conditions as access policy: + * 1. The BPNL of the requesting connector is equal to the BPNL of the partner + * 2. There's a valid CX membership credential present + * + * @param partner the partner to create the policy for + * @return the request body as a {@link JsonNode} + */ + public JsonNode buildBpnAndMembershipRestrictedPolicy(Partner partner) { + + List constraints = new ArrayList<>(); + + constraints.add(new PolicyConstraint( + "BusinessPartnerNumber", + "eq", + partner.getBpnl() + )); - var membershipOperator = MAPPER.createObjectNode(); - membershipConstraint.set("odrl:operator", membershipOperator); - membershipOperator.put("@id", "odrl:eq"); + constraints.add(new PolicyConstraint( + "Membership", + "eq", + "active" + )); - membershipConstraint.put("odrl:rightOperand", "active"); + JsonNode body = buildPolicy( + getBpnPolicyId(partner), + constraints, + null + ); + log.debug("Built bpn and membership access policy:\n{}", body.toPrettyString()); return body; } /** - * Creates a request body in order to register a policy that + * Creates a request body in order to register a contract policy that * allows only participants of the framework agreement. * * @return the request body */ public JsonNode buildFrameworkPolicy() { - var body = MAPPER.createObjectNode(); - var context = MAPPER.createObjectNode(); - context.put("odrl", ODRL_NAMESPACE); - body.set("@context", context); - body.put("@type", "PolicyDefinitionRequestDto"); - body.put("@id", CONTRACT_POLICY_ID); - var policy = MAPPER.createObjectNode(); - body.set("policy", policy); - policy.put("@type", "Policy"); - - var permissionsArray = MAPPER.createArrayNode(); - policy.set("odrl:permission", permissionsArray); + List constraints = new ArrayList<>(); - var permissionObject = MAPPER.createObjectNode(); - permissionsArray.add(permissionObject); - permissionObject.put("odrl:action", "USE"); + constraints.add(new PolicyConstraint( + CX_POLICY_NAMESPACE + "FrameworkAgreement", + "eq", + variablesService.getPurisFrameworkAgreementWithVersion() + )); - var constraintObject = MAPPER.createObjectNode(); - permissionObject.set("odrl:constraint", constraintObject); - constraintObject.put("@type", "LogicalConstraint"); - constraintObject.put("odrl:leftOperand", variablesService.getPurisFrameworkAgreement()); + constraints.add(new PolicyConstraint( + CX_POLICY_NAMESPACE + "UsagePurpose", + "eq", + variablesService.getPurisPuposeWithVersion() + )); - var operatorObject = MAPPER.createObjectNode(); - constraintObject.set("odrl:operator", operatorObject); - operatorObject.put("@id", "odrl:eq"); - constraintObject.put("odrl:rightOperand", "active"); + JsonNode body = buildPolicy( + CONTRACT_POLICY_ID, + constraints, + "cx-policy:profile2405" + ); + log.debug("Built framework agreement contract policy:\n{}", body.toPrettyString()); return body; } @@ -191,7 +228,7 @@ public JsonNode buildSubmodelContractDefinitionWithBpnRestrictedPolicy(String as public JsonNode buildDtrContractDefinitionForPartner(Partner partner) { var body = getEdcContextObject(); - body.put("@id", partner.getBpnl() +"_contractdefinition_for_dtr"); + body.put("@id", partner.getBpnl() + "_contractdefinition_for_dtr"); body.put("accessPolicyId", getBpnPolicyId(partner)); body.put("contractPolicyId", getBpnPolicyId(partner)); var assetsSelector = MAPPER.createObjectNode(); @@ -205,7 +242,7 @@ public JsonNode buildDtrContractDefinitionForPartner(Partner partner) { public JsonNode buildPartTypeInfoContractDefinitionForPartner(Partner partner) { var body = getEdcContextObject(); - body.put("@id", partner.getBpnl() +"_contractdefinition_for_PartTypeInfoAsset"); + body.put("@id", partner.getBpnl() + "_contractdefinition_for_PartTypeInfoAsset"); body.put("accessPolicyId", getBpnPolicyId(partner)); body.put("contractPolicyId", CONTRACT_POLICY_ID); var assetsSelector = MAPPER.createObjectNode(); @@ -235,28 +272,39 @@ private String getBpnPolicyId(Partner partner) { * * @param partner The Partner to negotiate with * @param dcatCatalogItem The catalog entry that describes the target asset. + * @param dspUrl The dspUrl if a specific (not from MAD Partner) needs to be used, not null * @return The request body */ - public JsonNode buildAssetNegotiationBody(Partner partner, JsonNode dcatCatalogItem) { - var body = MAPPER.createObjectNode(); - var contextNode = MAPPER.createObjectNode(); + public JsonNode buildAssetNegotiationBody(Partner partner, JsonNode dcatCatalogItem, String dspUrl) { + ObjectNode body = MAPPER.createObjectNode(); + ObjectNode contextNode = MAPPER.createObjectNode(); contextNode.put(VOCAB_KEY, EDC_NAMESPACE); contextNode.put("odrl", ODRL_NAMESPACE); body.set("@context", contextNode); - body.put("@type", "NegotiationInitiateRequestDto"); - body.put("connectorId", partner.getBpnl()); - body.put("connectorAddress", partner.getEdcUrl()); - body.put("consumerId", variablesService.getOwnBpnl()); - body.put("providerId", partner.getBpnl()); + body.put("@type", "ContractRequest"); + body.put("counterPartyAddress", dspUrl); body.put("protocol", "dataspace-protocol-http"); + + // extract policy and information from offer + // framework agreement and co has been checked during catalog request String assetId = dcatCatalogItem.get("@id").asText(); - var policyNode = dcatCatalogItem.get("odrl:hasPolicy"); - var offerNode = MAPPER.createObjectNode(); + JsonNode policyNode = dcatCatalogItem.get("odrl:hasPolicy"); + + ObjectNode targetIdObject = MAPPER.createObjectNode(); + targetIdObject.put("@id", assetId); + ((ObjectNode) policyNode).put("@context", "http://www.w3.org/ns/odrl.jsonld"); + ((ObjectNode) policyNode).set("odrl:target", targetIdObject); + ((ObjectNode) policyNode).put("assigner", partner.getBpnl()); + + ObjectNode offerNode = MAPPER.createObjectNode(); String offerId = policyNode.get("@id").asText(); offerNode.put("offerId", offerId); offerNode.put("assetId", assetId); offerNode.set("policy", policyNode); - body.set("offer", offerNode); + + body.set("policy", policyNode); + + log.debug("Created asset negotiation body:\n" + body.toPrettyString()); return body; } @@ -271,21 +319,25 @@ public JsonNode buildAssetNegotiationBody(Partner partner, JsonNode dcatCatalogI */ public JsonNode buildProxyPullRequestBody(Partner partner, String contractID, String assetId, String partnerEdcUrl) { var body = getEdcContextObject(); - body.put("@type", "TransferRequestDto"); body.put("connectorId", partner.getBpnl()); - body.put("connectorAddress", partnerEdcUrl); + body.put("counterPartyAddress", partnerEdcUrl); body.put("contractId", contractID); body.put("assetId", assetId); body.put("protocol", "dataspace-protocol-http"); body.put("managedResources", false); + body.put("transferType", "HttpData-PULL"); var dataDestination = MAPPER.createObjectNode(); dataDestination.put("type", "HttpProxy"); body.set("dataDestination", dataDestination); + // This private property is not evaluated in EDC 0.7.0 anymore due to data plane signalling + // EDRs are taken manually var privateProperties = MAPPER.createObjectNode(); privateProperties.put("receiverHttpEndpoint", variablesService.getEdrEndpoint()); body.set("privateProperties", privateProperties); + + log.debug("Built Proxy Pull Request:\n{}", body.toPrettyString()); return body; } @@ -413,6 +465,7 @@ private ObjectNode getAssetRegistrationContext() { context.put("cx-taxo", CX_TAXO_NAMESPACE); context.put("cx-common", CX_COMMON_NAMESPACE); context.put("dct", DCT_NAMESPACE); + context.put("aas-semantics", AAS_SEMANTICS_NAMESPACE); body.set("@context", context); body.put("@type", "Asset"); return body; @@ -433,5 +486,40 @@ private ObjectNode getEdcContextObject() { return node; } + /** + * A helper method returning a basic request object meant for policy interactions to be used to build other + * specific request bodies. + * + * @return A request body stub + */ + private ObjectNode getPolicyContextObject() { + ObjectNode node = MAPPER.createObjectNode(); + ArrayNode contextArray = MAPPER.createArrayNode(); + contextArray.add(ODRL_REMOTE_CONTEXT); + + ObjectNode contextObject = MAPPER.createObjectNode(); + contextObject.put("edc", EDC_NAMESPACE); + contextObject.put("cx-policy", CX_POLICY_NAMESPACE); + contextArray.add(contextObject); + + node.set("@context", contextArray); + return node; + } + + /** + * builds a body to terminate the transfer process + * + * @param reason why transfer is terminated + * @return transfer process termination request body + */ + public JsonNode buildTransferProcessTerminationBody(String reason) { + + ObjectNode body = getEdcContextObject(); + + body.put("@type", "TerminateTransfer"); + body.put("reason", reason); + + return body; + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java index 7fbb25e7..9c19600c 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java @@ -125,6 +125,24 @@ public class VariablesService { */ private String purisFrameworkAgreement; + @Value("${puris.frameworkagreement.version}") + /** + * The version of the framework agreement to be used. + */ + private String purisFrameworkAgreementVersion; + + @Value("${puris.purpose.name}") + /** + * The name of the purpose to be used for submodel contract policies. + */ + private String purisPurposeName; + + @Value("${puris.purpose.version}") + /** + * The version of the purpse to be used for submodel contract policies. + */ + private String purisPurposeVersion; + @Value("${puris.api.key}") /** * The key for accessing the api. @@ -241,4 +259,12 @@ public String getDeliverySubmodelApiAssetId() { public String getPartTypeSubmodelApiAssetId() { return "PartTypeInformationSubmodelApi@" + getOwnBpnl(); } + + public String getPurisFrameworkAgreementWithVersion() { + return getPurisFrameworkAgreement() + ":" + getPurisFrameworkAgreementVersion(); + } + + public String getPurisPuposeWithVersion() { + return getPurisPurposeName() + ":" + getPurisPurposeVersion(); + } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index afec35e0..ec851ede 100755 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -11,7 +11,10 @@ puris.productionsubmodel.apiassetid=${PURIS_PRODUCTIONSUBMODEL_APIASSETID:produc puris.demandsubmodel.apiassetid=${PURIS_DEMANDSUBMODEL_APIASSETID:demandsubmodel-api-asset} puris.deliverysubmodel.apiassetid=${PURIS_DELIVERYSUBMODEL_APIASSETID:deliverysubmodel-api-asset} puris.frameworkagreement.use=${PURIS_FRAMEWORKAGREEMENT_USE:false} -puris.frameworkagreement.credential=${PURIS_FRAMEWORKAGREEMENT_CREDENTIAL:FrameworkAgreement.traceability} +puris.frameworkagreement.credential=${PURIS_FRAMEWORKAGREEMENT_CREDENTIAL:Puris} +puris.frameworkagreement.version=${PURIS_FRAMEWORKAGREEMENT_VERSION:1.0} +puris.purpose.name=${PURIS_PURPOSE_NAME:cx.puris.base} +puris.purpose.version=${PURIS_PURPOSE_VERSION:1} puris.api.key=${PURIS_API_KEY:test} puris.dtr.url=${PURIS_DTR_URL:http://localhost:4243} @@ -23,7 +26,6 @@ puris.dtr.url=${PURIS_DTR_URL:http://localhost:4243} # In a real-world-scenario, you must then use this randomly generated CatenaX-Id for the lifetime of that # Material entity. puris.generatematerialcatenaxid=${PURIS_GENERATEMATERIALCATENAXID:true} - # DB Configuration spring.datasource.driver-class-name=${DATASOURCE_DRIVERCLASSNAME:org.postgresql.Driver} spring.datasource.url=${DATASOURCE_URL:jdbc:postgresql://localhost:5432/puris-db} @@ -38,31 +40,25 @@ edc.controlplane.key=${EDC_CONTROLPLANE_KEY:password} edc.controlplane.management.url=${EDC_CONTROLPLANE_MANAGEMENT_URL:http://customer-control-plane:8181/management} edc.controlplane.protocol.url=${EDC_CONTROLPLANE_PROTOCOL_URL:http://customer-control-plane:8184/api/v1/dsp} edc.dataplane.public.url=${EDC_DATAPLANE_PUBLIC_URL:http://customer-data-plane:8285/api/public/} - # Jackson (JSON) #spring.jackson.default-property-inclusion=non_empty #logging.level.org.hibernate.SQL=DEBUG #logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true - # Own BPNL own.bpnl=${OWN_BPNL:BPNL4444444444XX} - # Own name (self-description) own.name=${OWN_NAME:Control Unit Creator Inc.} - # Own BPNS (optional: if this is set, then set own.site.name as well) own.bpns=${OWN_BPNS:BPNS4444444444XX} # Name of Site (see above) own.site.name=${OWN_SITE:Control Unit Creator Production Site} - # If a BPNS is set, then this BPNA will be attached to it. # Otherwise, it will be attached immediately to the BPNL (see above) own.bpna=${OWN_BPNA:BPNA4444444444AA} own.streetandnumber=${OWN_STREETANDNUMBER:13th Street 47} own.zipcodeandcity=${OWN_ZIPCODEANDCITY:10011 New York} own.country=${OWN_COUNTRY:USA} - server.ssl.enabled=false #server.port=8443 #server.ssl.bundle=server @@ -70,6 +66,5 @@ server.ssl.enabled=false #spring.ssl.bundle.jks.server.keystore.location=file:ssl-certificates/application.p12 #spring.ssl.bundle.jks.server.keystore.password=testtest #spring.ssl.bundle.jks.server.keystore.type=PKCS12 - # run with: # ./mvnw spring-boot:run -Dspring-boot.run.arguments=--spring.config.location="./src/main/resources/application.properties" diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index 91252480..57d1390c 100755 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -8,7 +8,10 @@ puris.productionsubmodel.apiassetid=${PURIS_PRODUCTIONSUBMODEL_APIASSETID:produc puris.demandsubmodel.apiassetid=${PURIS_DEMANDSUBMODEL_APIASSETID:demandsubmodel-api-asset} puris.deliverysubmodel.apiassetid=${PURIS_DELIVERYSUBMODEL_APIASSETID:deliverysubmodel-api-asset} puris.frameworkagreement.use=${PURIS_FRAMEWORKAGREEMENT_USE:false} -puris.frameworkagreement.credential=${PURIS_FRAMEWORKAGREEMENT_CREDENTIAL:FrameworkAgreement.traceability} +puris.frameworkagreement.credential=${PURIS_FRAMEWORKAGREEMENT_CREDENTIAL:Puris} +puris.frameworkagreement.version=${PURIS_FRAMEWORKAGREEMENT_CREDENTIAL:1.0} +puris.purpose.name=${PURIS_PURPOSE_NAME:cx.puris.base} +puris.purpose.version=${PURIS_PURPOSE_VERSION:1} puris.api.key=${PURIS_API_KEY:test} puris.dtr.url=${PURIS_DTR_URL:http://localhost:4243} puris.generatematerialcatenaxid=${PURIS_GENERATEMATERIALCATENAXID:true} diff --git a/charts/puris/Chart.yaml b/charts/puris/Chart.yaml index 06142577..694945f8 100644 --- a/charts/puris/Chart.yaml +++ b/charts/puris/Chart.yaml @@ -35,7 +35,7 @@ dependencies: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 2.3.0 +version: 2.4.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/puris/README.md b/charts/puris/README.md index 31c5e44a..3f774f13 100644 --- a/charts/puris/README.md +++ b/charts/puris/README.md @@ -1,6 +1,6 @@ # puris -![Version: 2.3.0](https://img.shields.io/badge/Version-2.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0](https://img.shields.io/badge/AppVersion-1.0.0-informational?style=flat-square) +![Version: 2.0.1](https://img.shields.io/badge/Version-2.0.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0](https://img.shields.io/badge/AppVersion-1.0.0-informational?style=flat-square) A helm chart for Kubernetes deployment of PURIS @@ -75,7 +75,8 @@ $ helm install puris --namespace puris --create-namespace . | backend.puris.edc.dataplane.public.url | string | `"https://your-data-plane:8285/api/public/"` | Url of one of your data plane's public api | | backend.puris.edr.deletiontimer | int | `2` | Number of minutes before received authentication data of a consumer pull is removed from memory | | backend.puris.existingSecret | string | `"secret-backend-puris"` | Secret for backend passwords. For more information look into 'backend-secrets.yaml' file. | -| backend.puris.frameworkagreement.credential | string | `"FrameworkAgreement.traceability"` | The name of the framework agreement | +| backend.puris.frameworkagreement.credential | string | `"Puris"` | The name of the framework agreement. Starting with Uppercase and using CamelCase. | +| backend.puris.frameworkagreement.version | string | `"1.0"` | The version of the framework agreement, NEEDS TO BE PUT AS "STRING"! | | backend.puris.generatematerialcatenaxid | bool | `true` | Flag that decides whether the auto-generation feature of the puris backend is enabled. Since all Material entities are required to have a CatenaX-Id, you must enter any pre-existing CatenaX-Id via the materials-API of the backend, when you are inserting a new Material entity to the backend's database. If a CatenaX-Id was not assigned to your Material so far, then this feature can auto-generate one randomly. In a real-world-scenario, you must then use this randomly generated CatenaX-Id for the lifetime of that Material entity. | | backend.puris.itemstocksubmodel.apiassetid | string | `"itemstocksubmodel-api-asset"` | Asset ID for ItemStockSubmodel API | | backend.puris.jpa.hibernate.ddl-auto | string | `"create"` | Initialises SQL database with Hibernate property "create" to allow Hibernate to first drop all tables and then create new ones | @@ -89,6 +90,8 @@ $ helm install puris --namespace puris --create-namespace . | backend.puris.own.streetnumber | string | `"Musterstraße 110A"` | Own street and number | | backend.puris.own.zipcodeandcity | string | `"12345 Musterhausen"` | Own zipcode and city | | backend.puris.productionsubmodel.apiassetid | string | `"productionsubmodel-api-asset"` | Asset ID for ProductionSubmodel API | +| backend.puris.purpose.name | string | `"cx.puris.base"` | The name of the purpose to use for submodel contracts | +| backend.puris.purpose.version | string | `"1"` | The version of the purpose to use for submodel contracts. NEEDS TO BE PUT AS "STRING"! | | backend.readinessProbe | object | `{"failureThreshold":3,"initialDelaySeconds":120,"periodSeconds":25,"successThreshold":1,"timeoutSeconds":1}` | Checks if the pod is fully ready to operate | | backend.readinessProbe.failureThreshold | int | `3` | Number of failures (threshold) for a readiness probe | | backend.readinessProbe.initialDelaySeconds | int | `120` | Delay in seconds after which an initial readiness probe is checked | @@ -144,7 +147,7 @@ $ helm install puris --namespace puris --create-namespace . | frontend.puris.endpointDemand | string | `"demand"` | The endpoint for the demand submodel | | frontend.puris.endpointMaterialStocks | string | `"stockView/material-stocks"` | The endpoint for material stocks for the stock view | | frontend.puris.endpointMaterials | string | `"stockView/materials"` | The endpoint for materials for the stock view | -| frontend.puris.endpointPartnerOwnSites | string | `"partners/ownSites"` | The endpoint for the partners BPNS | +| frontend.puris.endpointPartner | string | `"partner"` | The endpoint for the partners BPNS | | frontend.puris.endpointProductStocks | string | `"stockView/product-stocks"` | The endpoint for product stocks for the stock view | | frontend.puris.endpointProduction | string | `"production"` | The endpoint for the production submodel | | frontend.puris.endpointProductionRange | string | `"production/range"` | The endpoint for the production range of the production submodel | diff --git a/charts/puris/templates/backend-deployment.yaml b/charts/puris/templates/backend-deployment.yaml index 4fe0bfb2..1b91a08b 100644 --- a/charts/puris/templates/backend-deployment.yaml +++ b/charts/puris/templates/backend-deployment.yaml @@ -149,6 +149,12 @@ spec: value: "{{ .Values.backend.puris.frameworkagreement.use }}" - name: PURIS_FRAMEWORKAGREEMENT_CREDENTIAL value: "{{ .Values.backend.puris.frameworkagreement.credential }}" + - name: PURIS_FRAMEWORKAGREEMENT_VERSION + value: "{{ .Values.backend.puris.frameworkagreement.version }}" + - name: PURIS_PURPOSE_NAME + value: "{{ .Values.backend.puris.purpose.name }}" + - name: PURIS_PURPOSE_VERSION + value: "{{ .Values.backend.puris.purpose.version }}" - name: PURIS_DTR_URL value: "{{ .Values.backend.puris.dtr.url }}" - name: PURIS_GENERATEMATERIALCATENAXID diff --git a/charts/puris/templates/frontend-deployment.yaml b/charts/puris/templates/frontend-deployment.yaml index af53a91a..38c0d5de 100644 --- a/charts/puris/templates/frontend-deployment.yaml +++ b/charts/puris/templates/frontend-deployment.yaml @@ -79,8 +79,8 @@ spec: value: "{{ .Values.frontend.puris.endpointUpdateReportedMaterialStocks }}" - name: ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS value: "{{ .Values.frontend.puris.endpointUpdateReportedProductStocks }}" - - name: ENDPOINT_PARTNER_OWNSITES - value: "{{ .Values.frontend.puris.endpointPartnerOwnSites }}" + - name: ENDPOINT_PARTNER + value: "{{ .Values.frontend.puris.endpointPartner }}" - name: ENDPOINT_DEMAND value: "{{ .Values.frontend.puris.endpointDemand }}" - name: ENDPOINT_PRODUCTION diff --git a/charts/puris/values.yaml b/charts/puris/values.yaml index c49c3de4..908424fc 100644 --- a/charts/puris/values.yaml +++ b/charts/puris/values.yaml @@ -185,8 +185,8 @@ frontend: endpointUpdateReportedMaterialStocks: stockView/update-reported-material-stocks?ownMaterialNumber= # -- The endpoint for triggering an update of your product stocks on your partners side endpointUpdateReportedProductStocks: stockView/update-reported-product-stocks?ownMaterialNumber= - # -- The endpoint for the partners BPNS - endpointPartnerOwnSites: partners/ownSites + # -- The endpoint for partner information + endpointPartner: partner # -- The endpoint for the demand submodel endpointDemand: demand # -- The endpoint for the production submodel @@ -432,8 +432,15 @@ backend: # -- Asset ID for DeliverySubmodel API apiassetid: deliverysubmodel-api-asset frameworkagreement: - # -- The name of the framework agreement - credential: FrameworkAgreement.traceability + # -- The name of the framework agreement. Starting with Uppercase and using CamelCase. + credential: Puris + # -- The version of the framework agreement, NEEDS TO BE PUT AS "STRING"! + version: "1.0" + purpose: + # -- The name of the purpose to use for submodel contracts + name: "cx.puris.base" + # -- The version of the purpose to use for submodel contracts. NEEDS TO BE PUT AS "STRING"! + version: "1" edr: # -- Number of minutes before received authentication data of a consumer pull is removed from memory deletiontimer: 2 diff --git a/frontend/.env b/frontend/.env index eb431426..82961c5f 100644 --- a/frontend/.env +++ b/frontend/.env @@ -12,7 +12,7 @@ VITE_ENDPOINT_REPORTED_MATERIAL_STOCKS=stockView/reported-material-stocks?ownMat VITE_ENDPOINT_REPORTED_PRODUCT_STOCKS=stockView/reported-product-stocks?ownMaterialNumber= VITE_ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS=stockView/update-reported-material-stocks?ownMaterialNumber= VITE_ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS=stockView/update-reported-product-stocks?ownMaterialNumber= -VITE_ENDPOINT_PARTNER_OWNSITES=partners/ownSites +VITE_ENDPOINT_PARTNER=partners VITE_ENDPOINT_DEMAND=demand VITE_ENDPOINT_PRODUCTION=production VITE_ENDPOINT_PRODUCTION_RANGE=production/range diff --git a/frontend/.env.dockerbuild b/frontend/.env.dockerbuild index e7e88171..ec2f7582 100644 --- a/frontend/.env.dockerbuild +++ b/frontend/.env.dockerbuild @@ -11,7 +11,7 @@ VITE_ENDPOINT_REPORTED_MATERIAL_STOCKS=\$ENDPOINT_REPORTED_MATERIAL_STOCKS VITE_ENDPOINT_REPORTED_PRODUCT_STOCKS=\$ENDPOINT_REPORTED_PRODUCT_STOCKS VITE_ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS=\$ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS VITE_ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS=\$ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS -VITE_ENDPOINT_PARTNER_OWNSITES=\$ENDPOINT_PARTNER_OWNSITES +VITE_ENDPOINT_PARTNER=\$ENDPOINT_PARTNER VITE_ENDPOINT_DEMAND=\$ENDPOINT_DEMAND VITE_ENDPOINT_PRODUCTION=\$ENDPOINT_PRODUCTION VITE_ENDPOINT_PRODUCTION_RANGE=\$ENDPOINT_PRODUCTION_RANGE diff --git a/frontend/src/config.json b/frontend/src/config.json index 598f7a1d..7b8d422a 100644 --- a/frontend/src/config.json +++ b/frontend/src/config.json @@ -12,7 +12,7 @@ "ENDPOINT_REPORTED_PRODUCT_STOCKS": "$ENDPOINT_REPORTED_PRODUCT_STOCKS", "ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS":"$ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS", "ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS":"$ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS", - "ENDPOINT_PARTNER_OWNSITES": "$ENDPOINT_PARTNER_OWNSITES", + "ENDPOINT_PARTNER": "$ENDPOINT_PARTNER", "ENDPOINT_DEMAND": "$ENDPOINT_DEMAND", "ENDPOINT_PRODUCTION": "$ENDPOINT_PRODUCTION", "ENDPOINT_PRODUCTION_RANGE": "$ENDPOINT_PRODUCTION_RANGE", diff --git a/frontend/src/features/edc/hooks/usePartners.ts b/frontend/src/features/edc/hooks/usePartners.ts new file mode 100644 index 00000000..34cee21c --- /dev/null +++ b/frontend/src/features/edc/hooks/usePartners.ts @@ -0,0 +1,34 @@ +/* +Copyright (c) 2024 Volkswagen AG +Copyright (c) 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 +*/ + +import {useFetch} from '@hooks/useFetch'; +import {config} from '@models/constants/config'; +import {Partner} from '@models/types/edc/partner'; + +export const usePartners = () => { + const endpoint = config.app.ENDPOINT_PARTNER + "/all"; + const {data: partners, isLoading: isLoadingPartners} = useFetch( + `${config.app.BACKEND_BASE_URL}${endpoint}` + ); + return { + partners, + isLoadingPartners, + }; +} diff --git a/frontend/src/features/stock-view/hooks/useSites.ts b/frontend/src/features/stock-view/hooks/useSites.ts index 1369c559..66ecb30c 100644 --- a/frontend/src/features/stock-view/hooks/useSites.ts +++ b/frontend/src/features/stock-view/hooks/useSites.ts @@ -18,12 +18,16 @@ under the License. SPDX-License-Identifier: Apache-2.0 */ -import { config } from '@models/constants/config'; -import { Site } from '@models/types/edc/site'; -import { useFetch } from '@hooks/useFetch'; +import {config} from '@models/constants/config'; +import {Site} from '@models/types/edc/site'; +import {useFetch} from '@hooks/useFetch'; export const useSites = () => { - const { data: sites, error: sitesError, isLoading: isLoadingSites, } = useFetch(config.app.BACKEND_BASE_URL + config.app.ENDPOINT_PARTNER_OWNSITES); + const { + data: sites, + error: sitesError, + isLoading: isLoadingSites, + } = useFetch(config.app.BACKEND_BASE_URL + config.app.ENDPOINT_PARTNER + "/ownSites"); return { sites, sitesError, diff --git a/frontend/src/hooks/edc/useCatalog.ts b/frontend/src/hooks/edc/useCatalog.ts index 59092671..758ce811 100644 --- a/frontend/src/hooks/edc/useCatalog.ts +++ b/frontend/src/hooks/edc/useCatalog.ts @@ -18,28 +18,31 @@ under the License. SPDX-License-Identifier: Apache-2.0 */ -import { config } from '@models/constants/config'; -import { RawCatalogData } from '@models/types/edc/catalog'; -import { useFetch } from '../useFetch'; -import { isErrorResponse } from '@util/helpers'; +import {config} from '@models/constants/config'; +import {RawCatalogData} from '@models/types/edc/catalog'; +import {useFetch} from '../useFetch'; +import {isErrorResponse} from '@util/helpers'; +import {Partner} from '@models/types/edc/partner'; -export const useCatalog = (edcUrl: string | null) => { +export const useCatalog = (partner: Partner | null) => { const { data, error, isLoading: isLoadingCatalog, - } = useFetch(edcUrl ? config.app.BACKEND_BASE_URL + 'edc/catalog?dspUrl=' + encodeURIComponent(edcUrl) : undefined); + } = useFetch(partner ? config.app.BACKEND_BASE_URL + + 'edc/catalog?dspUrl=' + encodeURIComponent(partner.edcUrl) + + '&partnerBpnl=' + encodeURIComponent(partner.bpnl) : undefined); const catalog = data && !isErrorResponse(data) ? (data['dcat:dataset']?.map((item) => { - return { - assetId: item['@id'], - assetType: item['dct:type']['@id'], - assetVersion: item['https://w3id.org/catenax/ontology/common#version'], - permission: item['odrl:hasPolicy'] && item['odrl:hasPolicy']['odrl:permission'], - prohibitions: item['odrl:hasPolicy'] && item['odrl:hasPolicy']['odrl:prohibition'], - obligations: item['odrl:hasPolicy'] && item['odrl:hasPolicy']['odrl:obligation'], - }; - }) ?? null) + return { + assetId: item['@id'], + assetType: item['https://purl.org/dc/terms/type']['@id'], + assetVersion: item['https://w3id.org/catenax/ontology/common#version'], + permission: item['odrl:hasPolicy'] && item['odrl:hasPolicy']['odrl:permission'], + prohibitions: item['odrl:hasPolicy'] && item['odrl:hasPolicy']['odrl:prohibition'], + obligations: item['odrl:hasPolicy'] && item['odrl:hasPolicy']['odrl:obligation'], + }; + }) ?? null) : null; const catalogError = error ?? (data && isErrorResponse(data) ? data : null); return { diff --git a/frontend/src/models/constants/config.ts b/frontend/src/models/constants/config.ts index eac5c5c0..c47ca853 100644 --- a/frontend/src/models/constants/config.ts +++ b/frontend/src/models/constants/config.ts @@ -32,7 +32,7 @@ const app = { ENDPOINT_REPORTED_PRODUCT_STOCKS: import.meta.env.VITE_ENDPOINT_REPORTED_PRODUCT_STOCKS.trim() as string, ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS: import.meta.env.VITE_ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS.trim() as string, ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS: import.meta.env.VITE_ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS.trim() as string, - ENDPOINT_PARTNER_OWNSITES: import.meta.env.VITE_ENDPOINT_PARTNER_OWNSITES.trim() as string, + ENDPOINT_PARTNER: import.meta.env.VITE_ENDPOINT_PARTNER.trim() as string, ENDPOINT_DEMAND: import.meta.env.VITE_ENDPOINT_DEMAND.trim() as string, ENDPOINT_PRODUCTION: import.meta.env.VITE_ENDPOINT_PRODUCTION.trim() as string, ENDPOINT_PRODUCTION_RANGE: import.meta.env.VITE_ENDPOINT_PRODUCTION_RANGE.trim() as string, diff --git a/frontend/src/models/types/edc/catalog.ts b/frontend/src/models/types/edc/catalog.ts index 9e2cff14..e9fe28c4 100644 --- a/frontend/src/models/types/edc/catalog.ts +++ b/frontend/src/models/types/edc/catalog.ts @@ -19,13 +19,13 @@ SPDX-License-Identifier: Apache-2.0 */ export type CatalogOperation = { - 'odrl:constraint': { - 'odrl:leftOperand': string; - 'odrl:operator': { - '@id': string; - }; - 'odrl:rightOperand': string; - }; + 'odrl:constraint': { + 'odrl:leftOperand': string; + 'odrl:operator': { + '@id': string; + }; + 'odrl:rightOperand': string; + }; }; export type CatalogPermission = { @@ -34,28 +34,28 @@ export type CatalogPermission = { 'odrl:type': string; } 'odrl:constraint': { - 'odrl:leftOperand': string; - 'odrl:operator': { - '@id': string; - }; - 'odrl:rightOperand': string; + 'odrl:leftOperand': string; + 'odrl:operator': { + '@id': string; + }; + 'odrl:rightOperand': string; } | { '@type': string, 'odrl:and': { - '@type': string, + '@type': string, 'odrl:leftOperand': string; 'odrl:operator': { '@id': string; }; 'odrl:rightOperand': string; }[] - }; + }; }; export type RawCatalogData = { 'dcat:dataset': { '@id': string; - 'dct:type': { + 'https://purl.org/dc/terms/type': { '@id': string; } 'https://w3id.org/catenax/ontology/common#version': string; diff --git a/frontend/src/models/types/edc/negotiation.ts b/frontend/src/models/types/edc/negotiation.ts index 38acd3b0..9bb56ac5 100644 --- a/frontend/src/models/types/edc/negotiation.ts +++ b/frontend/src/models/types/edc/negotiation.ts @@ -19,11 +19,11 @@ SPDX-License-Identifier: Apache-2.0 */ export type Negotiation = { - '@id': string; - 'edc:contractAgreementId'?: string; - 'edc:type': string; - 'edc:state': string; - 'edc:counterPartyId': string; - 'edc:counterPartyAddress': string; - 'edc:createdAt': string; + '@id': string; + 'contractAgreementId'?: string; + 'type': string; + 'state': string; + 'counterPartyId': string; + 'counterPartyAddress': string; + 'createdAt': string; }; diff --git a/frontend/src/models/types/edc/transfer.ts b/frontend/src/models/types/edc/transfer.ts index 833c5f9d..7e57ed8c 100644 --- a/frontend/src/models/types/edc/transfer.ts +++ b/frontend/src/models/types/edc/transfer.ts @@ -19,12 +19,12 @@ SPDX-License-Identifier: Apache-2.0 */ export type Transfer = { - '@id': string; - 'edc:correlationId': string; - 'edc:state': string; - 'edc:stateTimestamp': string; - 'edc:type': string; - 'edc:assetId': string; - 'edc:contractId': string; - 'edc:connectorId': string; + '@id': string; + 'correlationId': string; + 'state': string; + 'stateTimestamp': string; + 'type': string; + 'assetId': string; + 'contractId': string; + 'connectorId': string; }; diff --git a/frontend/src/views/CatalogView.tsx b/frontend/src/views/CatalogView.tsx index b7ae5ea9..0d28b5dc 100644 --- a/frontend/src/views/CatalogView.tsx +++ b/frontend/src/views/CatalogView.tsx @@ -19,19 +19,21 @@ under the License. SPDX-License-Identifier: Apache-2.0 */ -import { Input, LoadingButton } from '@catena-x/portal-shared-components'; -import { useCatalog } from '@hooks/edc/useCatalog'; -import { useRef, useState } from 'react'; -import { CatalogOperation } from '@models/types/edc/catalog'; -import { Card } from '@mui/material'; -import { getCatalogOperator } from '@util/helpers'; +import {Input, LoadingButton} from '@catena-x/portal-shared-components'; +import {useCatalog} from '@hooks/edc/useCatalog'; +import {useRef, useState} from 'react'; +import {CatalogOperation} from '@models/types/edc/catalog'; +import {Autocomplete, Card} from '@mui/material'; +import {getCatalogOperator} from '@util/helpers'; +import {Partner} from '@models/types/edc/partner'; +import {usePartners} from "@features/edc/hooks/usePartners.ts"; type OperationListProps = { title: string; operations: CatalogOperation[]; }; -const OperationList = ({ title, operations }: OperationListProps) => { +const OperationList = ({title, operations}: OperationListProps) => { return ( <> {title} @@ -53,27 +55,37 @@ const OperationList = ({ title, operations }: OperationListProps) => { }; export const CatalogView = () => { - const [edcUrl, setEdcUrl] = useState(null); - const { catalog, catalogError, isLoadingCatalog } = useCatalog(edcUrl); - const urlRef = useRef(null); + const {partners} = usePartners(); + const [partner, setPartner] = useState(null); + const {catalog, catalogError, isLoadingCatalog} = useCatalog(partner); + const partnerRef = useRef(null); return (

View EDC Catalog

-
- (urlRef.current = event.target.value)} +
+ option?.name ?? ''} + renderInput={(params) => ( + + )} + onChange={(event, newValue) => (partnerRef.current = newValue)} + isOptionEqualToValue={(option, value) => option?.uuid === value?.uuid} + className="flex-grow" />
setEdcUrl(urlRef?.current)} + onButtonClick={() => setPartner(partnerRef?.current)} />
@@ -81,7 +93,7 @@ export const CatalogView = () => {
    {catalog.map((item, index) => ( -
  • +
  • Catalog Item

    @@ -98,28 +110,37 @@ export const CatalogView = () => {

    Asset condition(s):

    - {'odrl:and' in item.permission['odrl:constraint'] ? - (item.permission['odrl:constraint']['odrl:and'].map(constraint => ( + {'odrl:and' in item.permission['odrl:constraint'] ? ( + Array.isArray(item.permission['odrl:constraint']['odrl:and']) ? ( + item.permission['odrl:constraint']['odrl:and'].map(constraint => ( +
    + {constraint['odrl:leftOperand'] + ' '} + {getCatalogOperator(constraint['odrl:operator']['@id']) + ' '} + {constraint['odrl:rightOperand']} +
    + )) + ) : (
    - {constraint['odrl:leftOperand'] + ' '} - {getCatalogOperator(constraint['odrl:operator']['@id']) + ' '} - {constraint['odrl:rightOperand']} -
    - ))) : - ( -
    - {item.permission['odrl:constraint']['odrl:leftOperand'] + ' '} - {getCatalogOperator(item.permission['odrl:constraint']['odrl:operator']['@id']) + ' '} - {item.permission['odrl:constraint']['odrl:rightOperand']} + {item.permission['odrl:constraint']['odrl:and']['odrl:leftOperand'] + ' '} + {getCatalogOperator(item.permission['odrl:constraint']['odrl:and']['odrl:operator']['@id']) + ' '} + {item.permission['odrl:constraint']['odrl:and']['odrl:rightOperand']}
    ) - } + ) : ( +
    + {item.permission['odrl:constraint']['odrl:leftOperand'] + ' '} + {getCatalogOperator(item.permission['odrl:constraint']['odrl:operator']['@id']) + ' '} + {item.permission['odrl:constraint']['odrl:rightOperand']} +
    + )}
    - - + +
  • @@ -127,9 +148,10 @@ export const CatalogView = () => { ))}
) : catalogError != null ? ( -
There was an error retrieving the Catalog from {edcUrl}
+
There was an error retrieving the Catalog + from {partner?.edcUrl}
) : ( -
{`No Catalog available for ${edcUrl}`}
+
{`No Catalog available for ${partner?.edcUrl}`}
)}
); diff --git a/frontend/src/views/NegotiationView.tsx b/frontend/src/views/NegotiationView.tsx index 8716265d..b31ab3d8 100644 --- a/frontend/src/views/NegotiationView.tsx +++ b/frontend/src/views/NegotiationView.tsx @@ -21,14 +21,14 @@ SPDX-License-Identifier: Apache-2.0 import Card from '@mui/material/Card'; -import { useNegotiations } from '@hooks/edc/useNegotiations'; -import { Negotiation } from '@models/types/edc/negotiation'; +import {useNegotiations} from '@hooks/edc/useNegotiations'; +import {Negotiation} from '@models/types/edc/negotiation'; type NegotiationCardProps = { negotiation: Negotiation; }; -const NegotiationCard = ({negotiation }: NegotiationCardProps) => { +const NegotiationCard = ({negotiation}: NegotiationCardProps) => { return (

Negotiation

@@ -40,27 +40,27 @@ const NegotiationCard = ({negotiation }: NegotiationCardProps) => {
Aggreement Id: - {negotiation['edc:contractAgreementId']} + {negotiation['contractAgreementId']}
Type: - {negotiation['edc:type']} + {negotiation['type']}
State: - {negotiation['edc:state']} + {negotiation['state']}
CounterParty: - {negotiation['edc:counterPartyId']} + {negotiation['counterPartyId']}
Counterparty EDC URL: - {negotiation['edc:counterPartyAddress']} + {negotiation['counterPartyAddress']}
Timestamp: - {new Date(negotiation['edc:createdAt']).toLocaleString()} + {new Date(negotiation['createdAt']).toLocaleString()}
@@ -69,19 +69,20 @@ const NegotiationCard = ({negotiation }: NegotiationCardProps) => { }; export const NegotiationView = () => { - const { negotiations } = useNegotiations(); + const {negotiations} = useNegotiations(); return (

Negotiation

    {negotiations && negotiations.length > 0 ? ( negotiations.map((negotiation) => ( -
  • - +
  • +
  • )) ) : ( -

    No negotiations found. This list will be updated when Negotiations happen.

    +

    No negotiations found. This list will be updated when Negotiations + happen.

    )}
diff --git a/frontend/src/views/TransferView.tsx b/frontend/src/views/TransferView.tsx index 4cae9b86..be97a27d 100644 --- a/frontend/src/views/TransferView.tsx +++ b/frontend/src/views/TransferView.tsx @@ -21,14 +21,14 @@ SPDX-License-Identifier: Apache-2.0 import Card from '@mui/material/Card'; -import { useTransfers } from '@hooks/edc/useTransfers'; -import { Transfer } from '@models/types/edc/transfer'; +import {useTransfers} from '@hooks/edc/useTransfers'; +import {Transfer} from '@models/types/edc/transfer'; type TransferCardProps = { transfer: Transfer; }; -const TransferCard = ({ transfer }: TransferCardProps) => { +const TransferCard = ({transfer}: TransferCardProps) => { return (

Transfer

@@ -40,31 +40,31 @@ const TransferCard = ({ transfer }: TransferCardProps) => {
Correlation Id: - {transfer['edc:correlationId']} + {transfer['correlationId']}
State: - {transfer['edc:state']} + {transfer['state']}
State Timestamp: - {new Date(transfer['edc:stateTimestamp']).toLocaleString()} + {new Date(transfer['stateTimestamp']).toLocaleString()}
Type: - {transfer['edc:type']} + {transfer['type']}
Asset Id: - {transfer['edc:assetId']} + {transfer['assetId']}
Contract Id: - {transfer['edc:contractId']} + {transfer['contractId']}
Connector Id: - {transfer['edc:connectorId']} + {transfer['connectorId']}
@@ -73,19 +73,20 @@ const TransferCard = ({ transfer }: TransferCardProps) => { }; export const TransferView = () => { - const { transfers } = useTransfers(); + const {transfers} = useTransfers(); return (

Transfers

    {transfers && transfers.length > 0 ? ( transfers.map((transfer) => ( -
  • - +
  • +
  • )) ) : ( -

    No transfers found. This Page will be updated as soon as there are transfers.

    +

    No transfers found. This Page will be updated as soon as there are + transfers.

    )}
diff --git a/local/INSTALL.md b/local/INSTALL.md index 0a0386f4..7d0034d4 100644 --- a/local/INSTALL.md +++ b/local/INSTALL.md @@ -54,6 +54,7 @@ First start the infrastructure by navigating your shell to the local folder and docker compose -f docker-compose-infrastructure.yaml up ``` + After starting the central infrastructure, initialize the bdrs-service. To do so, just run the script `seed-bdrs.sh` created during the run of script `generate-keys.sh`. ```shell -sh seed-brds.sh +sh seed-bdrs.sh ``` Then start the PURIS demonstrator containers via: diff --git a/local/docker-compose-infrastructure.yaml b/local/docker-compose-infrastructure.yaml index 5a1766f5..e131347f 100644 --- a/local/docker-compose-infrastructure.yaml +++ b/local/docker-compose-infrastructure.yaml @@ -20,16 +20,17 @@ version: "3" services: - miw: - image: tractusx/managed-identity-wallet:0.4.0 - container_name: miw - env_file: - - ./miw/infrastructure.properties - ports: - - "127.0.0.1:8000:80" - - "127.0.0.1:8090:8090" - networks: - - miw-net + # Outcommented as not updated for R24.05 but scheduled for R24.08 + # miw: + # image: tractusx/managed-identity-wallet:0.4.0 + # container_name: miw + # env_file: + # - ./miw/infrastructure.properties + # ports: + # - "127.0.0.1:8000:80" + # - "127.0.0.1:8090:8090" + # networks: + # - miw-net postgres: image: postgres:15.4-alpine diff --git a/local/docker-compose.yaml b/local/docker-compose.yaml index df9fb8e2..d9de6c78 100644 --- a/local/docker-compose.yaml +++ b/local/docker-compose.yaml @@ -40,7 +40,7 @@ services: - ENDPOINT_REPORTED_PRODUCT_STOCKS=stockView/reported-product-stocks?ownMaterialNumber= - ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS=stockView/update-reported-material-stocks?ownMaterialNumber= - ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS=stockView/update-reported-product-stocks?ownMaterialNumber= - - ENDPOINT_PARTNER_OWNSITES=partners/ownSites + - ENDPOINT_PARTNER=partners - ENDPOINT_DEMAND=demand - ENDPOINT_PRODUCTION=production - ENDPOINT_PRODUCTION_RANGE=production/range @@ -84,10 +84,10 @@ services: postgres-all: condition: service_healthy healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3/shell-descriptors"] + test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3/shell-descriptors" ] interval: 4s timeout: 3s - retries: 15 + retries: 20 ports: - "127.0.0.1:4243:4243" environment: @@ -191,7 +191,7 @@ services: - ENDPOINT_REPORTED_PRODUCT_STOCKS=stockView/reported-product-stocks?ownMaterialNumber= - ENDPOINT_UPDATE_REPORTED_MATERIAL_STOCKS=stockView/update-reported-material-stocks?ownMaterialNumber= - ENDPOINT_UPDATE_REPORTED_PRODUCT_STOCKS=stockView/update-reported-product-stocks?ownMaterialNumber= - - ENDPOINT_PARTNER_OWNSITES=partners/ownSites + - ENDPOINT_PARTNER=partners - ENDPOINT_DEMAND=demand - ENDPOINT_PRODUCTION=production - ENDPOINT_PRODUCTION_RANGE=production/range @@ -235,10 +235,10 @@ services: postgres-all: condition: service_healthy healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3/shell-descriptors"] + test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3/shell-descriptors" ] interval: 4s timeout: 3s - retries: 15 + retries: 20 ports: - "127.0.0.1:4244:4243" environment: diff --git a/local/tractus-x-edc/config/customer/control-plane.properties b/local/tractus-x-edc/config/customer/control-plane.properties index d62e7a03..da7ef02e 100644 --- a/local/tractus-x-edc/config/customer/control-plane.properties +++ b/local/tractus-x-edc/config/customer/control-plane.properties @@ -29,13 +29,13 @@ edc.iam.trusted-issuer.portal.id=did:web:mock-util-service/trusted-issuer tx.iam.iatp.credentialservice.url=http://mock-util-service:80 # don't use https during did resolving edc.iam.did.web.use.https=false -# old MIW config, can be ignored TODO remove -tx.ssi.oauth.token.url=http://keycloak:8080/realms/miw_test/protocol/openid-connect/token -tx.ssi.oauth.client.id=${CUSTOMER_OAUTH_CLIENT_ID} -tx.ssi.oauth.client.secret.alias=${CUSTOMER_OAUTH_SECRET_ALIAS} -tx.ssi.miw.url=http://miw -tx.ssi.miw.authority.id=BPNL000000000000 -tx.ssi.endpoint.audience=http://customer-control-plane:8184/api/v1/dsp +# old MIW config (<0.7.0), can be ignored till MIW is back +#tx.ssi.oauth.token.url=http://keycloak:8080/realms/miw_test/protocol/openid-connect/token +#tx.ssi.oauth.client.id=${CUSTOMER_OAUTH_CLIENT_ID} +#tx.ssi.oauth.client.secret.alias=${CUSTOMER_OAUTH_SECRET_ALIAS} +#tx.ssi.miw.url=http://miw +#tx.ssi.miw.authority.id=BPNL000000000000 +#tx.ssi.endpoint.audience=http://customer-control-plane:8184/api/v1/dsp # HashiCorp vault related configuration edc.vault.hashicorp.url=http://vault:8200 edc.vault.hashicorp.health.check.enabled=false diff --git a/local/tractus-x-edc/config/customer/puris-backend.properties b/local/tractus-x-edc/config/customer/puris-backend.properties index 8ed2802c..30a91ebd 100644 --- a/local/tractus-x-edc/config/customer/puris-backend.properties +++ b/local/tractus-x-edc/config/customer/puris-backend.properties @@ -7,16 +7,18 @@ puris.itemstocksubmodel.apiassetid=itemstocksubmodel-api-asset puris.productionsubmodel.apiassetid=productionsubmodel-api-asset puris.demandsubmodel.apiassetid=demandsubmodel-api-asset puris.deliverysubmodel.apiassetid=deliverysubmodel-api-asset -puris.frameworkagreement.credential=FrameworkAgreement.traceability +puris.frameworkagreement.credential=Puris +puris.frameworkagreement.version=1.0 +puris.purpose.name=cx.puris.base +puris.purpose.version=1 +logging.level.org.eclipse.tractusx.puris.backend=INFO puris.api.key=${CUSTOMER_BACKEND_API_KEY} puris.dtr.url=http://dtr-customer:4243 puris.generatematerialcatenaxid=true - edc.controlplane.key=${EDC_API_PW} edc.controlplane.management.url=http://customer-control-plane:8181/management edc.controlplane.protocol.url=http://customer-control-plane:8184/api/v1/dsp edc.dataplane.public.url=http://customer-data-plane:8285/api/public/ - own.bpnl=BPNL4444444444XX own.name=Control Unit Creator Inc. own.bpns=BPNS4444444444XX diff --git a/local/tractus-x-edc/config/supplier/control-plane.properties b/local/tractus-x-edc/config/supplier/control-plane.properties index d8f54d67..ac0b8f2b 100644 --- a/local/tractus-x-edc/config/supplier/control-plane.properties +++ b/local/tractus-x-edc/config/supplier/control-plane.properties @@ -30,13 +30,13 @@ edc.iam.trusted-issuer.portal.id=did:web:mock-util-service/trusted-issuer tx.iam.iatp.credentialservice.url=http://mock-util-service:80 # don't use https during did resolving edc.iam.did.web.use.https=false -# old MIW config, can be ignored TODO remove -tx.ssi.oauth.token.url=http://keycloak:8080/realms/miw_test/protocol/openid-connect/token -tx.ssi.oauth.client.id=${SUPPLIER_OAUTH_CLIENT_ID} -tx.ssi.oauth.client.secret.alias=${SUPPLIER_OAUTH_SECRET_ALIAS} -tx.ssi.miw.url=http://miw -tx.ssi.miw.authority.id=BPNL000000000000 -tx.ssi.endpoint.audience=http://supplier-control-plane:9184/api/v1/dsp +# old MIW config (<0.7.0), can be ignored till MIW is back +#tx.ssi.oauth.token.url=http://keycloak:8080/realms/miw_test/protocol/openid-connect/token +#tx.ssi.oauth.client.id=${SUPPLIER_OAUTH_CLIENT_ID} +#tx.ssi.oauth.client.secret.alias=${SUPPLIER_OAUTH_SECRET_ALIAS} +#tx.ssi.miw.url=http://miw +#tx.ssi.miw.authority.id=BPNL000000000000 +#tx.ssi.endpoint.audience=http://supplier-control-plane:9184/api/v1/dsp # HashiCorp vault related configuration edc.vault.hashicorp.url=http://vault:8200 edc.vault.hashicorp.health.check.enabled=false diff --git a/local/tractus-x-edc/config/supplier/data-plane.properties b/local/tractus-x-edc/config/supplier/data-plane.properties index ac8be2ff..95e01a79 100644 --- a/local/tractus-x-edc/config/supplier/data-plane.properties +++ b/local/tractus-x-edc/config/supplier/data-plane.properties @@ -7,7 +7,7 @@ web.http.public.path=/api/public # new in 0.7.0 data plane signalling, replaced control web.http.signaling.port=9283 web.http.signaling.path=/api/signaling -# new in 0.3.3 - why do we need the management in a data plane? TODO removed? +# new in 0.3.3 - why do we need the management in a data plane? web.http.management.port=9293 web.http.management.path=/api/v1/data # Validation endpoint of controlplane diff --git a/local/tractus-x-edc/config/supplier/puris-backend.properties b/local/tractus-x-edc/config/supplier/puris-backend.properties index 4c704874..cfa2a826 100644 --- a/local/tractus-x-edc/config/supplier/puris-backend.properties +++ b/local/tractus-x-edc/config/supplier/puris-backend.properties @@ -7,16 +7,18 @@ puris.itemstocksubmodel.apiassetid=itemstocksubmodel-api-asset puris.productionsubmodel.apiassetid=productionsubmodel-api-asset puris.demandsubmodel.apiassetid=demandsubmodel-api-asset puris.deliverysubmodel.apiassetid=deliverysubmodel-api-asset -puris.frameworkagreement.credential=FrameworkAgreement.traceability +puris.frameworkagreement.credential=Puris +puris.frameworkagreement.version=1.0 +puris.purpose.name=cx.puris.base +puris.purpose.version=1 +logging.level.org.eclipse.tractusx.puris.backend=INFO puris.api.key=${SUPPLIER_BACKEND_API_KEY} puris.dtr.url=http://dtr-supplier:4243 puris.generatematerialcatenaxid=true - edc.controlplane.key=${EDC_API_PW} edc.controlplane.management.url=http://supplier-control-plane:9181/management edc.controlplane.protocol.url=http://supplier-control-plane:9184/api/v1/dsp edc.dataplane.public.url=http://supplier-data-plane:9285/api/public/ - own.bpnl=BPNL1234567890ZZ own.name=Semiconductor Supplier Inc. own.bpns=BPNS1234567890ZZ