From 34deac9c6693db56dbe761ddf82af1d623db7fe9 Mon Sep 17 00:00:00 2001 From: Sachin Argade Date: Fri, 10 May 2024 20:31:33 +0530 Subject: [PATCH 1/2] dt access api use in digital twin processing --- .../step/DigitalTwinAccessRuleFacilator.java | 169 ++++++++++++++++++ .../step/DigitalTwinUseCaseHandler.java | 90 +++++++--- .../flyway/V22__add_new_role_for_download.sql | 0 .../V24__Alter_process_report_table.sql | 0 .../V25__add_new_role_for_pcf_exchange.sql | 0 .../facilitator/DigitalTwinsUtility.java | 74 +++++--- 6 files changed, 288 insertions(+), 45 deletions(-) create mode 100644 modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinAccessRuleFacilator.java rename "modules/sde-core/src/main/resources/flyway/V22__add_new_role_for_download.sql\n" => modules/sde-core/src/main/resources/flyway/V22__add_new_role_for_download.sql (100%) rename "modules/sde-core/src/main/resources/flyway/V24__Alter_process_report_table.sql\n" => modules/sde-core/src/main/resources/flyway/V24__Alter_process_report_table.sql (100%) rename "modules/sde-core/src/main/resources/flyway/V25__add_new_role_for_pcf_exchange.sql\n" => modules/sde-core/src/main/resources/flyway/V25__add_new_role_for_pcf_exchange.sql (100%) diff --git a/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinAccessRuleFacilator.java b/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinAccessRuleFacilator.java new file mode 100644 index 000000000..4246bd3be --- /dev/null +++ b/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinAccessRuleFacilator.java @@ -0,0 +1,169 @@ +/******************************************************************************** + * Copyright (c) 2024 T-Systems International GmbH + * 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 + ********************************************************************************/ + +package org.eclipse.tractusx.sde.core.submodel.executor.step; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.tractusx.sde.common.configuration.properties.SDEConfigurationProperties; +import org.eclipse.tractusx.sde.common.constants.SubmoduleCommonColumnsConstant; +import org.eclipse.tractusx.sde.common.entities.PolicyModel; +import org.eclipse.tractusx.sde.common.exception.NoDataFoundException; +import org.eclipse.tractusx.sde.common.exception.ServiceException; +import org.eclipse.tractusx.sde.common.submodel.executor.DatabaseUsecaseStep; +import org.eclipse.tractusx.sde.common.submodel.executor.Step; +import org.eclipse.tractusx.sde.common.utils.PolicyOperationUtil; +import org.eclipse.tractusx.sde.digitaltwins.facilitator.DigitalTwinsFacilitator; +import org.eclipse.tractusx.sde.digitaltwins.facilitator.DigitalTwinsUtility; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonObject; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DigitalTwinAccessRuleFacilator extends Step { + + private final DigitalTwinsFacilitator digitalTwinFacilitator; + + private final DigitalTwinsUtility digitalTwinsUtility; + + private final SDEConfigurationProperties sdeConfigProperties; + + @Qualifier("DatabaseUsecaseHandler") + private final DatabaseUsecaseStep databaseUseCaseStep; + + private String createRuleTemplate = """ + { + "validFrom": "2020-01-02T03:04:05Z", + "validTo": "4999-01-02T03:04:05Z", + "description": "ACME policy within set validity period", + "policyType": "AAS", + "policy": { + "accessRules": [ + { + "attribute": "bpn", + "operator": "eq", + "value": "%s" + }, + { + "attribute": "mandatorySpecificAssetIds", + "operator": "includes", + "values": [ %s ] + }, + { + "attribute": "visibleSpecificAssetIdNames", + "operator": "includes", + "values": [ %s ] + }, + { + "attribute": "visibleSemanticIds", + "operator": "includes", + "values": [ + { + "attribute": "modelUrn", + "operator": "eq", + "value": "%s" + } + ] + } + ] + } + } + """; + + @SneakyThrows + public void createAccessRule(Integer rowIndex, ObjectNode jsonObject, Map specificAssetIds, + PolicyModel policy, String sematicId) { + try { + + deleteAllExistingDTAccessRulesIfExist(rowIndex, jsonObject); + + List accessBPNList = PolicyOperationUtil.getAccessBPNList(policy); + List accessruleIds = new ArrayList<>(); + + for (String bpn : accessBPNList) { + + String requestTemplate = String.format(createRuleTemplate, bpn, + digitalTwinsUtility.createAccessRuleMandatorySpecificAssetIds(specificAssetIds), + digitalTwinsUtility.createAccessRuleVisibleSpecificAssetIdNames(specificAssetIds), sematicId); + + JsonNode requestBody = new ObjectMapper().readTree(requestTemplate); + + JsonNode response = digitalTwinFacilitator + .createAccessControlsRule(sdeConfigProperties.getManufacturerId(), requestBody); + accessruleIds.add(response.get("id").asText()); + } + + jsonObject.put(SubmoduleCommonColumnsConstant.SHELL_ACCESS_RULE_IDS, StringUtils.join(accessruleIds, ",")); + + } catch (Exception e) { + log.error(String.format("Row: %s: DigitalTwin exception: unable to create digital twin access rule %s", + rowIndex, specificAssetIds.toString()) + ", because: " + e.getMessage()); + throw new ServiceException( + String.format("Row: %s: DigitalTwin exception: unable to create digital twin access rule %s", + rowIndex, specificAssetIds.toString())); + } + } + + private void deleteAllExistingDTAccessRulesIfExist(Integer rowIndex, ObjectNode jsonObject) { + + String identifier = getIdentifier(jsonObject, getIdentifierOfModel()); + JsonObject datinRow = null; + try { + databaseUseCaseStep.init(getSubmodelSchema()); + datinRow = databaseUseCaseStep.readCreatedTwinsDetails(identifier); + + if (datinRow != null && !datinRow.get(SubmoduleCommonColumnsConstant.SHELL_ACCESS_RULE_IDS).isJsonNull()) { + + String accessRules = datinRow.get(SubmoduleCommonColumnsConstant.SHELL_ACCESS_RULE_IDS).getAsString(); + + Arrays.asList(accessRules.split(",")).stream() + .filter(StringUtils::isNotBlank) + .forEach(str -> { + try { + str = str.trim(); + digitalTwinFacilitator.deleteAccessControlsRule(accessRules, + sdeConfigProperties.getManufacturerId()); + } catch (Exception e) { + log.error(String.format( + "Row: %s: DigitalTwin: unable to delete existing DT access rule identifier %s, access rule id %s", rowIndex, + identifier, str)); + } + }); + } + } catch (NoDataFoundException e) { + log.debug(String.format("Row: %s: DigitalTwin: no existing digital twin access rule data found %s", rowIndex, + identifier)); + } + } +} \ No newline at end of file diff --git a/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinUseCaseHandler.java b/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinUseCaseHandler.java index 4078df9c3..98f81499a 100644 --- a/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinUseCaseHandler.java +++ b/modules/sde-core/src/main/java/org/eclipse/tractusx/sde/core/submodel/executor/step/DigitalTwinUseCaseHandler.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import java.util.TreeMap; import org.apache.commons.lang3.StringUtils; import org.eclipse.tractusx.sde.common.configuration.properties.SDEConfigurationProperties; @@ -34,6 +35,7 @@ import org.eclipse.tractusx.sde.common.submodel.executor.Step; import org.eclipse.tractusx.sde.common.utils.JsonObjectUtility; import org.eclipse.tractusx.sde.common.utils.UUIdGenerator; +import org.eclipse.tractusx.sde.digitaltwins.entities.common.Endpoint; import org.eclipse.tractusx.sde.digitaltwins.entities.request.CreateSubModelRequest; import org.eclipse.tractusx.sde.digitaltwins.entities.request.ShellDescriptorRequest; import org.eclipse.tractusx.sde.digitaltwins.entities.request.ShellLookupRequest; @@ -52,9 +54,11 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; -@Service("DigitalTwinUseCaseHandler") +@Service("digitalTwinUseCaseHandler") @RequiredArgsConstructor public class DigitalTwinUseCaseHandler extends Step implements DigitalTwinUsecaseStep { + + private static final String FORWARD_SLASH = "/"; private final DigitalTwinsFacilitator digitalTwinFacilitator; @@ -63,7 +67,9 @@ public class DigitalTwinUseCaseHandler extends Step implements DigitalTwinUsecas private final SDEConfigurationProperties sdeConfigProperties; private final DigitalTwinLookUpInRegistry digitalTwinLookUpInRegistry; - + + private final DigitalTwinAccessRuleFacilator digitalTwinAccessRuleFacilator; + public void delete(Integer rowIndex, JsonObject jsonObject, String delProcessId, String refProcessId) { String shellId = JsonObjectUtility.getValueFromJsonObject(jsonObject, SubmoduleCommonColumnsConstant.SHELL_ID); String submodelId = JsonObjectUtility.getValueFromJsonObject(jsonObject, @@ -102,6 +108,9 @@ public JsonNode run(Integer rowIndex, ObjectNode jsonObject, String processId, P SubModelResponse foundSubmodel = findSubmoduleInShells(jsonObject, List.of(shellId)); checkAndCreateSubmodulIfNotExist(rowIndex, jsonObject, shellId, aasDescriptorRequest, foundSubmodel); + + digitalTwinAccessRuleFacilator.init(getSubmodelSchema()); + digitalTwinAccessRuleFacilator.createAccessRule(rowIndex, jsonObject, specificAssetIds, policy, getsemanticIdOfModel()); } catch (Exception e) { throw new CsvHandlerUseCaseException(rowIndex, ": DigitalTwins: " + e.getMessage()); @@ -157,25 +166,46 @@ private String createShell(Map specificAssetIds, ShellDescriptor public JsonNode checkAndCreateSubmodulIfNotExist(Integer rowIndex, ObjectNode jsonObject, String shellId, ShellDescriptorRequest aasDescriptorRequest, SubModelResponse foundSubmodel) { - String identification = findIdentificationForSubmodule(rowIndex, jsonObject); + Map identification = findIdentificationForSubmodule(rowIndex, jsonObject); + String path = getSubmoduleUriPathOfSubmodule(); + + String submodelRequestidentifier= identification.get("submodelRequestidentifier"); + String submodelIdentifier = identification.get("submodelIdentifier"); + + if (StringUtils.isNotBlank(path)) { + path = FORWARD_SLASH + getNameOfModel() + FORWARD_SLASH + path + FORWARD_SLASH + + submodelRequestidentifier; + } + + CreateSubModelRequest createSubModelRequest = digitalTwinsUtility.getCreateSubModelRequest(shellId, + getsemanticIdOfModel(), getIdShortOfModel(), submodelIdentifier, path, + getSubmodelShortDescriptionOfModel()); + if (foundSubmodel == null) { - logDebug(String.format("No submodels for '%s'", shellId)); - - CreateSubModelRequest createSubModelRequest = digitalTwinsUtility.getCreateSubModelRequest(shellId, - getsemanticIdOfModel(), getIdShortOfModel(), identification, getNameOfModel(), - getSubmoduleUriPathOfSubmodule(), getSubmodelShortDescriptionOfModel()); - digitalTwinFacilitator.updateShellDetails(shellId, aasDescriptorRequest, createSubModelRequest); jsonObject.put(SubmoduleCommonColumnsConstant.SUBMODULE_ID, createSubModelRequest.getId()); } else { // There is no need to send submodel because of nothing to change in it so // sending null of it - jsonObject.put(SubmoduleCommonColumnsConstant.SUBMODULE_ID, foundSubmodel.getId()); - digitalTwinFacilitator.updateShellDetails(shellId, aasDescriptorRequest, null); - logDebug("Complete Digital Twins Update Update Digital Twins"); + boolean isSubmodelRequestidentifierSame = false; + if(foundSubmodel.getEndpoints()!=null) { + isSubmodelRequestidentifierSame = foundSubmodel.getEndpoints().stream() + .map(Endpoint::getProtocolInformation) + .anyMatch(ele-> ele.getEndpointAddress().contains(submodelRequestidentifier)); + } + + if(!(foundSubmodel.getId().equals(submodelIdentifier)) || !isSubmodelRequestidentifierSame ) { + logDebug(String.format("Found submodel but need to update submodels for '%s'", shellId)); + digitalTwinFacilitator.updateShellDetails(shellId, aasDescriptorRequest, createSubModelRequest); + jsonObject.put(SubmoduleCommonColumnsConstant.SUBMODULE_ID, createSubModelRequest.getId()); + } else { + jsonObject.put(SubmoduleCommonColumnsConstant.SUBMODULE_ID, foundSubmodel.getId()); + digitalTwinFacilitator.updateShellDetails(shellId, aasDescriptorRequest, null); + logDebug("Complete Digital Twins Update Update Digital Twins"); + } } return jsonObject; @@ -205,7 +235,9 @@ private SubModelResponse findSubmoduleInShells(ObjectNode jsonObject, List allGlobalFiled = jArray.asList().stream() .filter(ele -> { JsonElement refElement = ele.getAsJsonObject().get("ref"); - return refElement!= null && refElement.getAsString().equals("shellGlobalAssetId"); + return refElement != null && refElement.isJsonPrimitive() + && refElement.getAsJsonPrimitive().isString() + && refElement.getAsJsonPrimitive().getAsString().equals("shellGlobalAssetId"); }) .toList(); if (!allGlobalFiled.isEmpty()) { @@ -220,10 +252,13 @@ private SubModelResponse findSubmoduleInShells(ObjectNode jsonObject, List findIdentificationForSubmodule(Integer rowIndex, ObjectNode jsonObject) { + + String submodelIdentifier =null; String identificationField = extractExactFieldName(getIdentifierOfModel()); + + List databaseIdentifierFields = getDatabaseIdentifierSpecsOfModel(); + JsonArray jArray = checkIsAutoPopulatedfieldsSubmodel(); List allAutoPopulateField = null; @@ -238,19 +273,28 @@ private String findIdentificationForSubmodule(Integer rowIndex, ObjectNode jsonO for (JsonElement jsonElement : allAutoPopulateField) { JsonObject asJsonObject = jsonElement.getAsJsonObject().get("ref").getAsJsonObject(); - String shemaIdentificationKey = jsonElement.getAsJsonObject().get("key").getAsString(); + String shemaIdentificationKey = extractExactFieldName(jsonElement.getAsJsonObject().get("key").getAsString()); String identificationLocal = findIdentificationForSubmodule(rowIndex, jsonObject, asJsonObject); jsonObject.put(shemaIdentificationKey, identificationLocal); + if (identificationField.equals(shemaIdentificationKey)) { - identification = identificationLocal; + submodelIdentifier = identificationLocal; } } - } else { - identification = UUIdGenerator.getUrnUuid(); + } + + if(submodelIdentifier == null) { + submodelIdentifier = UUIdGenerator.getUrnUuid(); } - - return identification; + + String submodelRequestidentifier = getDatabaseIdentifierValues(jsonObject, databaseIdentifierFields); + + Map output= new TreeMap<>(); + output.put("submodelIdentifier", submodelIdentifier); + output.put("submodelRequestidentifier", submodelRequestidentifier); + + return output; } private String findIdentificationForSubmodule(Integer rowIndex, ObjectNode jsonObject, JsonObject asJsonObject) { @@ -333,4 +377,4 @@ public ShellDescriptorRequest getShellDescriptorRequest(String nameAtManufacture specificAssetIds, policy); } -} +} \ No newline at end of file diff --git "a/modules/sde-core/src/main/resources/flyway/V22__add_new_role_for_download.sql\n" b/modules/sde-core/src/main/resources/flyway/V22__add_new_role_for_download.sql similarity index 100% rename from "modules/sde-core/src/main/resources/flyway/V22__add_new_role_for_download.sql\n" rename to modules/sde-core/src/main/resources/flyway/V22__add_new_role_for_download.sql diff --git "a/modules/sde-core/src/main/resources/flyway/V24__Alter_process_report_table.sql\n" b/modules/sde-core/src/main/resources/flyway/V24__Alter_process_report_table.sql similarity index 100% rename from "modules/sde-core/src/main/resources/flyway/V24__Alter_process_report_table.sql\n" rename to modules/sde-core/src/main/resources/flyway/V24__Alter_process_report_table.sql diff --git "a/modules/sde-core/src/main/resources/flyway/V25__add_new_role_for_pcf_exchange.sql\n" b/modules/sde-core/src/main/resources/flyway/V25__add_new_role_for_pcf_exchange.sql similarity index 100% rename from "modules/sde-core/src/main/resources/flyway/V25__add_new_role_for_pcf_exchange.sql\n" rename to modules/sde-core/src/main/resources/flyway/V25__add_new_role_for_pcf_exchange.sql diff --git a/modules/sde-external-services/digital-twins/src/main/java/org/eclipse/tractusx/sde/digitaltwins/facilitator/DigitalTwinsUtility.java b/modules/sde-external-services/digital-twins/src/main/java/org/eclipse/tractusx/sde/digitaltwins/facilitator/DigitalTwinsUtility.java index 35a5f393e..4c9b02c3a 100644 --- a/modules/sde-external-services/digital-twins/src/main/java/org/eclipse/tractusx/sde/digitaltwins/facilitator/DigitalTwinsUtility.java +++ b/modules/sde-external-services/digital-twins/src/main/java/org/eclipse/tractusx/sde/digitaltwins/facilitator/DigitalTwinsUtility.java @@ -1,6 +1,6 @@ /******************************************************************************** - * Copyright (c) 2022, 2024 T-Systems International GmbH - * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation + * Copyright (c) 2022,2024 T-Systems International GmbH + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -28,8 +28,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; -import org.apache.commons.lang3.StringUtils; import org.eclipse.tractusx.sde.common.constants.CommonConstants; import org.eclipse.tractusx.sde.common.entities.PolicyModel; import org.eclipse.tractusx.sde.common.utils.PolicyOperationUtil; @@ -60,8 +60,6 @@ @Getter public class DigitalTwinsUtility { - private static final String FORWARD_SLASH = "/"; - private static final String PUBLIC_READABLE = "PUBLIC_READABLE"; @Value(value = "${manufacturerId}") @@ -104,23 +102,12 @@ private String resizeShortId(String str) { @SneakyThrows public CreateSubModelRequest getCreateSubModelRequest(String shellId, String sematicId, String idShortofModel, - String submodel, String productIdPath, String description) { - String identification = UUIdGenerator.getUrnUuid(); - return getCreateSubModelRequest(shellId, sematicId, idShortofModel, identification, submodel, productIdPath, - description); - } - - @SneakyThrows - public CreateSubModelRequest getCreateSubModelRequest(String shellId, String sematicId, String idShortofModel, - String identification, String submodel, String productIdPath, String description) { + String identification, String path, String description) { SemanticId semanticId = SemanticId.builder().type(CommonConstants.EXTERNAL_REFERENCE) - .keys(List.of(new Keys(CommonConstants.GLOBAL_REFERENCE, sematicId))).build(); - - if (StringUtils.isNotBlank(productIdPath)) - productIdPath = FORWARD_SLASH + submodel + FORWARD_SLASH + productIdPath + FORWARD_SLASH + identification; + .keys(List.of(new Keys(CommonConstants.SUBMODEL, sematicId))).build(); - List endpoints = prepareDtEndpoint(shellId, identification, productIdPath); + List endpoints = prepareDtEndpoint(shellId, identification, path); MultiLanguage engLang = MultiLanguage.builder().language("en").text(description).build(); @@ -128,12 +115,12 @@ public CreateSubModelRequest getCreateSubModelRequest(String shellId, String sem .description(List.of(engLang)).endpoints(endpoints).build(); } - public List prepareDtEndpoint(String shellId, String submodelIdentification, String productIdPath) { + public List prepareDtEndpoint(String shellId, String submodelIdentification, String path) { List endpoints = new ArrayList<>(); endpoints.add(Endpoint.builder().endpointInterface(CommonConstants.INTERFACE) .protocolInformation(ProtocolInformation.builder() - .endpointAddress(digitalTwinEdcDataplaneEndpoint + productIdPath) + .endpointAddress(digitalTwinEdcDataplaneEndpoint + path) .endpointProtocol(CommonConstants.HTTP) .endpointProtocolVersion(List.of(CommonConstants.ENDPOINT_PROTOCOL_VERSION)) .subProtocol(CommonConstants.SUB_PROTOCOL) @@ -144,6 +131,49 @@ public List prepareDtEndpoint(String shellId, String submodelIdentific .build()); return endpoints; } + + public String createAccessRuleMandatorySpecificAssetIds(Map specificAssetIds) { + StringBuilder sb= new StringBuilder(); + specificAssetIds.entrySet().stream().forEach(ele->{ + if(sb.isEmpty()) { + sb.append(extractedMandatorySpecificAssetIds(ele)); + } else { + sb.append(","+extractedMandatorySpecificAssetIds(ele) ); + } + }); + return sb.toString(); + } + + private String extractedMandatorySpecificAssetIds(Entry ele) { + return "{" + + "\"attribute\":\""+ele.getKey()+"\"," + + "\"operator\":\"eq\"," + + "\"value\":\""+ele.getValue()+"\"" + + "}"; + } + + public String createAccessRuleVisibleSpecificAssetIdNames(Map specificAssetIds) { + + StringBuilder sb= new StringBuilder(); + specificAssetIds.entrySet().stream().forEach(ele->{ + if(sb.isEmpty()) { + sb.append(extractedVisibleSpecificAssetIdNames(ele)); + } else { + sb.append(","+extractedVisibleSpecificAssetIdNames(ele)); + } + }); + return sb.toString(); + } + + private String extractedVisibleSpecificAssetIdNames(Entry ele) { + return "{" + + "\"attribute\":\"name\"," + + "\"operator\":\"eq\"," + + "\"value\":\""+ele.getKey()+"\"" + + "}"; + } + + @SneakyThrows public List getSpecificAssetIds(Map specificAssetIds, PolicyModel policy) { @@ -230,4 +260,4 @@ public String encodeValueAsBase64Utf8(String string) { return Base64.getUrlEncoder().encodeToString(string.getBytes()); } -} +} \ No newline at end of file From 6565674f6085caeed6e460ae984f79956fac473f Mon Sep 17 00:00:00 2001 From: Sachin Argade Date: Fri, 10 May 2024 22:04:20 +0530 Subject: [PATCH 2/2] Update change log file --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f725d6b3..458992be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Supporting new submodel Singlelevelbomasplanned. - Support EDC 7. - Added new files for digital twin access rule support. -- Refactor code for pcf, dt access API, EDC 7. +- Refactor code for pcf, dt access API, EDC 7. +- Dt access api use in digital twin processing ### Fixed - Remove garbage character from 'edc_request_template' path. Fixed [#147](https://github.com/eclipse-tractusx/managed-simple-data-exchanger-backend/issues/147).