Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The **need for configuration updates** is **marked bold**.
- Added Data exchange approval edc integration ([#1173](https://github.com/eclipse-tractusx/puris/pull/1173))
- Added frontend implementation of data exhcange requests and approvals ([#1175](https://github.com/eclipse-tractusx/puris/pull/1175))
- Updated documentation files with new feature information ([#1190](https://github.com/eclipse-tractusx/puris/pull/1190))
- Implement support for PartTypeInformation 2.0.0 submodel ([#1199](https://github.com/eclipse-tractusx/puris/pull/1199))

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public JsonNode createProductRegistrationRequestBody(Material material, String p
submodelDescriptorsArray.add(createSubmodelObject(AssetType.DELIVERY_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, hrefs.anonymizedHref(), variablesService.getDeliveryAnonymizedSubmodelApiAssetId()));
submodelDescriptorsArray.add(createSubmodelObject(AssetType.PRODUCTION_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, hrefs.anonymizedHref(), variablesService.getProductionAnonymizedSubmodelApiAssetId()));
submodelDescriptorsArray.add(createSubmodelObject(AssetType.SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL.URN_SEMANTIC_ID, hrefs.href(), variablesService.getSingleLevelBomAsPlannedSubmodelApiAssetId()));
submodelDescriptorsArray.add(createPartTypeLegacySubmodelObject(material.getOwnMaterialNumber()));
submodelDescriptorsArray.add(createPartTypeSubmodelObject(material.getOwnMaterialNumber()));

log.debug("Created body for product {}\n{}", material.getOwnMaterialNumber(), body.toPrettyString());
Expand Down Expand Up @@ -256,6 +257,14 @@ private JsonNode createSubmodelObject(String semanticId, String href, String ass
return requestSubmodelObject;
}

private JsonNode createPartTypeLegacySubmodelObject(String materialId) {
String href = variablesService.getEdcDataplanePublicUrl();
href = href.endsWith("/") ? href : href + "/";
href += Base64.getEncoder().encodeToString(materialId.getBytes(StandardCharsets.UTF_8));
href += "/submodel";
return createSubmodelObject(AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL.URN_SEMANTIC_ID, href, variablesService.getPartTypeLegacySubmodelApiAssetId());
}

private JsonNode createPartTypeSubmodelObject(String materialId) {
String href = variablesService.getEdcDataplanePublicUrl();
href = href.endsWith("/") ? href : href + "/";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public enum AssetType {
DELIVERY_SUBMODEL("urn:samm:io.catenax.delivery_information:2.0.0#DeliveryInformation", "$value", "DeliveryInformation", "2.0"),
NOTIFICATION("urn:samm:io.catenax.demand_and_capacity_notification:3.0.0#DemandAndCapacityNotification", "none", "none", "3.0"),
DAYS_OF_SUPPLY("urn:samm:io.catenax.days_of_supply:2.0.0#DaysOfSupply", "$value", "DaysOfSupply", "2.0"),
PART_TYPE_INFORMATION_SUBMODEL("urn:samm:io.catenax.part_type_information:1.0.0#PartTypeInformation", "$value", "none", "1.0"),
PART_TYPE_INFORMATION_LEGACY_SUBMODEL("urn:samm:io.catenax.part_type_information:1.0.0#PartTypeInformation", "$value", "none", "1.0"),
PART_TYPE_INFORMATION_SUBMODEL("urn:samm:io.catenax.part_type_information:2.0.0#PartTypeInformation", "$value", "none", "2.0"),
DATA_EXCHANGE_REQUEST("urn:samm:io.catenax.data_exchange_request:1.0.0#DataExchangeRequest", "none", "none", "1.0"),
ITEM_STOCK_ANONYMIZED_SUBMODEL("urn:samm:io.catenax.item_stock_anonymized:1.0.0#ItemStockAnonymized", "$value", "none", "1.0"),
DELIVERY_ANONYMIZED_SUBMODEL("urn:samm:io.catenax.delivery_information_anonymized:1.0.0#DeliveryInformationAnonymized", "$value", "none", "1.0"),
Expand All @@ -54,6 +55,8 @@ public static AssetType fromUrn(String urnSemanticId) {
AssetType.NOTIFICATION;
case "urn:samm:io.catenax.days_of_supply:2.0.0#DaysOfSupply" -> AssetType.DAYS_OF_SUPPLY;
case "urn:samm:io.catenax.part_type_information:1.0.0#PartTypeInformation" ->
AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL;
case "urn:samm:io.catenax.part_type_information:2.0.0#PartTypeInformation" ->
AssetType.PART_TYPE_INFORMATION_SUBMODEL;
case "urn:samm:io.catenax.data_exchange_request:1.0.0#DataExchangeRequest" ->
AssetType.DATA_EXCHANGE_REQUEST;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright (c) 2026 Volkswagen AG

See the NOTICE file(s) distributed with this work for additional
information regarding copyright ownership.

This program and the accompanying materials are made available under the
terms of the Apache License, Version 2.0 which is available at
https://www.apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.

SPDX-License-Identifier: Apache-2.0
*/
package org.eclipse.tractusx.puris.backend.common.edc.domain.model;

import jakarta.persistence.Entity;
import lombok.ToString;

@Entity
@ToString(callSuper = true)
public class PartTypeLegacyContractMapping extends ContractMapping {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright (c) 2026 Volkswagen AG

See the NOTICE file(s) distributed with this work for additional
information regarding copyright ownership.

This program and the accompanying materials are made available under the
terms of the Apache License, Version 2.0 which is available at
https://www.apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.

SPDX-License-Identifier: Apache-2.0
*/
package org.eclipse.tractusx.puris.backend.common.edc.domain.repository;

import org.eclipse.tractusx.puris.backend.common.edc.domain.model.ContractMapping;
import org.eclipse.tractusx.puris.backend.common.edc.domain.model.PartTypeLegacyContractMapping;
import org.springframework.stereotype.Repository;

@Repository
public interface PartTypeLegacyContractMappingRepository extends GeneralContractMappingRepository<PartTypeLegacyContractMapping> {
@Override
default Class<? extends ContractMapping> getType() {
return PartTypeLegacyContractMapping.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,17 @@ public boolean registerAssetsInitially() {
AssetType.SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL.URN_SEMANTIC_ID
)));
result &= assetRegistration;
log.info("Registration of PartTypeInformation 1.0.0 submodel successful {}", (assetRegistration = registerPartTypeInfoSubmodelAsset()));
log.info("Registration of PartTypeInformationLegacy 1.0.0 submodel successful {}", (assetRegistration = registerSubmodelAsset(
variablesService.getPartTypeLegacySubmodelApiAssetId(),
variablesService.getParttypeInformationLegacyServerendpoint(),
AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL.URN_SEMANTIC_ID
)));
result &= assetRegistration;
log.info("Registration of PartTypeInformation 2.0.0 submodel successful {}", (assetRegistration = registerSubmodelAsset(
variablesService.getPartTypeSubmodelApiAssetId(),
variablesService.getParttypeInformationServerendpoint(),
AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID
)));
result &= assetRegistration;
log.info("Registration of self-contracts successful {}", (assetRegistration = createPolicyAndContractDefForOwnPartner()));
result &= assetRegistration;
Expand All @@ -275,7 +285,8 @@ public boolean createPolicyAndContractDefForPartner(Partner partner) {
result &= createSubmodelContractDefinitionForPartner(AssetType.NOTIFICATION.URN_SEMANTIC_ID, variablesService.getNotificationApiAssetId(), partner);
result &= createSubmodelContractDefinitionForPartner(AssetType.DATA_EXCHANGE_REQUEST.URN_SEMANTIC_ID, variablesService.getDataExchangeRequestApiAssetId(), partner);
result &= createSubmodelContractDefinitionForPartner(AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID, variablesService.getDaysOfSupplySubmodelApiAssetId(), partner);
return createSubmodelContractDefinitionForPartner(AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, variablesService.getPartTypeSubmodelApiAssetId(), partner) && result;
result &= createSubmodelContractDefinitionForPartner(AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, variablesService.getPartTypeSubmodelApiAssetId(), partner) && result;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result &= createSubmodelContractDefinitionForPartner(AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, variablesService.getPartTypeSubmodelApiAssetId(), partner) && result;
result &= createSubmodelContractDefinitionForPartner(AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, variablesService.getPartTypeSubmodelApiAssetId(), partner);

return createSubmodelContractDefinitionForPartner(AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL.URN_SEMANTIC_ID, variablesService.getPartTypeLegacySubmodelApiAssetId(), partner) && result;
}

/**
Expand Down Expand Up @@ -416,11 +427,6 @@ private boolean registerDtrAsset() {
return sendAssetRegistrationRequest(body, "DTR");
}

private boolean registerPartTypeInfoSubmodelAsset() {
var body = edcRequestBodyBuilder.buildPartTypeInfoSubmodelRegistrationBody();
return sendAssetRegistrationRequest(body, variablesService.getPartTypeSubmodelApiAssetId());
}

private boolean registerSubmodelAsset(String assetId, String endpoint, String semanticId) {
var body = edcRequestBodyBuilder.buildSubmodelRegistrationBody(assetId, endpoint, semanticId);
return sendAssetRegistrationRequest(body, assetId);
Expand Down Expand Up @@ -856,7 +862,8 @@ private JsonNode getSubmodelFromPartner(MaterialPartnerRelation mpr, AssetType t
case DELIVERY_ANONYMIZED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.DELIVERY_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, direction);
case PRODUCTION_ANONYMIZED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.PRODUCTION_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, direction);
case SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL.URN_SEMANTIC_ID, direction);
case PART_TYPE_INFORMATION_SUBMODEL -> fetchPartTypeSubmodelData(mpr);
case PART_TYPE_INFORMATION_LEGACY_SUBMODEL -> fetchSubmodelData(mpr, AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL.URN_SEMANTIC_ID, mpr.getPartnerMaterialNumber(), mpr.getPartner().getBpnl());
case PART_TYPE_INFORMATION_SUBMODEL -> fetchSubmodelData(mpr, AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, mpr.getPartnerMaterialNumber(), mpr.getPartner().getBpnl());
};
boolean failed = true;
try {
Expand Down Expand Up @@ -1044,11 +1051,6 @@ private SubmodelData fetchSubmodelDataByDirection(MaterialPartnerRelation mpr, S
return fetchSubmodelData(mpr, semanticId, manufacturerPartId, manufacturerId);
}

private SubmodelData fetchPartTypeSubmodelData(MaterialPartnerRelation mpr) {
return fetchSubmodelData(mpr, "urn:samm:io.catenax.part_type_information:1.0.0#PartTypeInformation",
mpr.getPartnerMaterialNumber(), mpr.getPartner().getBpnl());
}

private record SubmodelData(String assetId, String dspUrl, String href) {
}

Expand Down Expand Up @@ -1302,7 +1304,8 @@ private boolean negotiateContractForSubmodel(MaterialPartnerRelation mpr, AssetT
case ITEM_STOCK_ANONYMIZED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.ITEM_STOCK_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, direction);
case DELIVERY_ANONYMIZED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.DELIVERY_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, direction);
case PRODUCTION_ANONYMIZED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.PRODUCTION_ANONYMIZED_SUBMODEL.URN_SEMANTIC_ID, direction);
case PART_TYPE_INFORMATION_SUBMODEL -> fetchPartTypeSubmodelData(mpr);
case PART_TYPE_INFORMATION_LEGACY_SUBMODEL -> fetchSubmodelData(mpr, AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL.URN_SEMANTIC_ID, mpr.getPartnerMaterialNumber(), mpr.getPartner().getBpnl());
case PART_TYPE_INFORMATION_SUBMODEL -> fetchSubmodelData(mpr, AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, mpr.getPartnerMaterialNumber(), mpr.getPartner().getBpnl());
case SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL.URN_SEMANTIC_ID, direction);
};
Map<String, String> equalFilters = new HashMap<>();
Expand Down Expand Up @@ -1411,18 +1414,41 @@ public boolean negotiateContract(Partner partner, String assetId, AssetType type
}

/**
* This method will return the partnerCXId from the supplier partner and
* for the material that are contained in the given MaterialPartnerRelation.
* Returns the partner's CatenaX-Id for the material in the given MaterialPartnerRelation.
* <p>
* If the partner is not a supplier for that material, we can't expect to find a
* result a that partner's PartType Submodel API.
* PartTypeInformation 2.0.0 is attempted first, since it is the version used by default.
* Only if the partner does not offer it, the legacy version 1.0.0 is used as a fallback.
*
* @param mpr the MaterialPartnerRelation
* @return the partner's CXid for that material
* @return the partner's CX Id for that material, or null if it could not be obtained
*/
public String getCxIdFromPartTypeInformation(MaterialPartnerRelation mpr) {
var data = getSubmodelFromPartner(mpr, AssetType.PART_TYPE_INFORMATION_SUBMODEL, null, 1);
return data.get("catenaXId").asText();
String cxId = fetchCxId(mpr, AssetType.PART_TYPE_INFORMATION_SUBMODEL);
if (cxId != null) {
return cxId;
}
log.info("PartTypeInformation 2.0.0 unavailable at partner {} for {}, falling back to 1.0.0",
mpr.getPartner().getBpnl(), mpr.getMaterial().getOwnMaterialNumber());
return fetchCxId(mpr, AssetType.PART_TYPE_INFORMATION_LEGACY_SUBMODEL);
}

private String fetchCxId(MaterialPartnerRelation mpr, AssetType type) {
String cxIdProperty = switch (type) {
case PART_TYPE_INFORMATION_SUBMODEL -> "globalAssetId";
case PART_TYPE_INFORMATION_LEGACY_SUBMODEL -> "catenaXId";
default -> throw new IllegalArgumentException("Unsupported type for PartTypeInformation: " + type);
};
try {
var data = getSubmodelFromPartner(mpr, type, null, 1);
if (data == null) {
return null;
}
var cxIdNode = data.get(cxIdProperty);
return cxIdNode == null ? null : cxIdNode.asText();
} catch (Exception e) {
log.warn("Could not obtain {} for {} from partner {}", cxIdProperty, type, mpr.getPartner().getBpnl());
return null;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.ItemStockAnonymizedContractMappingRepository;
import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.ItemStockContractMappingRepository;
import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.PartTypeContractMappingRepository;
import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.PartTypeLegacyContractMappingRepository;
import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.ProductionContractMappingRepository;
import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.SingleLevelBomAsPlannedContractMappingRepository;
import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner;
Expand Down Expand Up @@ -82,6 +83,9 @@ public class EdcContractMappingService {
@Autowired
private PartTypeContractMappingRepository partTypeContractMappingRepository;

@Autowired
private PartTypeLegacyContractMappingRepository partTypeLegacyContractMappingRepository;

@Autowired
private DataExchangeRequestContractMappingRepository dataExchangeRequestContractMappingRepository;
private SingleLevelBomAsPlannedContractMappingRepository singleLevelBomAsPlannedContractMappingRepository;
Expand Down Expand Up @@ -151,6 +155,7 @@ private GeneralContractMappingRepository<? extends ContractMapping> getContractM
case ITEM_STOCK_ANONYMIZED_SUBMODEL -> itemStockAnonymizedContractMappingRepository;
case DELIVERY_ANONYMIZED_SUBMODEL -> deliveryAnonymizedContractMappingRepository;
case PRODUCTION_ANONYMIZED_SUBMODEL -> productionAnonymizedContractMappingRepository;
case PART_TYPE_INFORMATION_LEGACY_SUBMODEL -> partTypeLegacyContractMappingRepository;
case PART_TYPE_INFORMATION_SUBMODEL -> partTypeContractMappingRepository;
case DATA_EXCHANGE_REQUEST -> dataExchangeRequestContractMappingRepository;
case SINGLE_LEVEL_BOM_AS_PLANNED_SUBMODEL -> singleLevelBomAsPlannedContractMappingRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,20 +313,6 @@ public JsonNode buildDtrContractDefinitionForPartner(Partner partner) {
return body;
}

public JsonNode buildPartTypeInfoContractDefinitionForPartner(Partner partner) {
var body = getEdcContextObject();
body.put("@id", partner.getBpnl() + "_contractdefinition_for_PartTypeInfoAsset");
body.put("accessPolicyId", getBpnPolicyId(partner));
body.put("contractPolicyId", partner.getPolicyProfileVersion().CONTRACT_POLICY_ID);
var assetsSelector = MAPPER.createObjectNode();
body.set("assetsSelector", assetsSelector);
assetsSelector.put("@type", "CriterionDto");
assetsSelector.put("operandLeft", JsonLdConstants.EDC_NAMESPACE + "id");
assetsSelector.put("operator", "=");
assetsSelector.put("operandRight", getPartTypeInfoAssetId());
return body;
}

/**
* This method helps to ensure that the buildContractDefinitionWithBpnRestrictedPolicy uses the
* same policy-id as the one that is created with the buildContractDefinitionWithBpnRestrictedPolicy
Expand Down Expand Up @@ -527,43 +513,10 @@ public JsonNode createDataAddressObject(String endpoint, String proxyMethodAndBo
return dataAddress;
}

public JsonNode buildPartTypeInfoSubmodelRegistrationBody() {
var body = getAssetRegistrationContext();
body.put("@id", getPartTypeInfoAssetId());
var propertiesObject = MAPPER.createObjectNode();
body.set("properties", propertiesObject);
var dctTypeObject = MAPPER.createObjectNode();
propertiesObject.set("dct:type", dctTypeObject);
dctTypeObject.put("@id", JsonLdConstants.CX_TAXO_NAMESPACE + "Submodel");
propertiesObject.put("cx-common:version", "3.0");
var semanticIdObject = MAPPER.createObjectNode();
propertiesObject.set("aas-semantics:semanticId", semanticIdObject);
semanticIdObject.put("@id", "urn:samm:io.catenax.part_type_information:1.0.0#PartTypeInformation");
var dataAddress = MAPPER.createObjectNode();
String url = variablesService.getParttypeInformationServerendpoint();
if (!url.endsWith("/")) {
url += "/";
}
dataAddress.put("@type", "DataAddress");
dataAddress.put("proxyPath", "true");
dataAddress.put("proxyQueryParams", "false");
dataAddress.put("proxyMethod", "false");
dataAddress.put("type", "HttpData");
dataAddress.put("baseUrl", url);
dataAddress.put("authKey", "x-api-key");
dataAddress.put("authCode", variablesService.getApiKey());
body.set("dataAddress", dataAddress);
return body;
}

private String getDtrAssetId() {
return "DigitalTwinRegistryId@" + variablesService.getOwnBpnl();
}

private String getPartTypeInfoAssetId() {
return variablesService.getPartTypeSubmodelApiAssetId();
}

private ObjectNode getAssetRegistrationContext() {
var body = MAPPER.createObjectNode();
var context = MAPPER.createObjectNode();
Expand Down
Loading
Loading