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 (