From 9b388458d3a6f250cae0e2701c30a9b777e20974 Mon Sep 17 00:00:00 2001 From: vibhutikumar <160819926+vibhutikumar07@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:43:56 +0530 Subject: [PATCH 1/6] Release_v1.10.2 updated release version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c188cfe7..ab4efefd 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.10.1-SNAPSHOT + 1.10.2 21 ${java.version} ${java.version} From 4cd039956d8de525535e33154d0e76035a48aca7 Mon Sep 17 00:00:00 2001 From: vibhutikumar <160819926+vibhutikumar07@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:48:55 +0530 Subject: [PATCH 2/6] Update action.yml --- .github/actions/newrelease/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/newrelease/action.yml b/.github/actions/newrelease/action.yml index 04296d55..96598079 100644 --- a/.github/actions/newrelease/action.yml +++ b/.github/actions/newrelease/action.yml @@ -34,8 +34,8 @@ runs: #./ensure-license.sh git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git checkout -b develop + git checkout -b Release_v1.10.2 git add cap-notebook/version.txt git commit -am "Update version to $VERSION" - git push --set-upstream origin develop + git push --set-upstream origin Release_v1.10.2 shell: bash From 8b0de77086a5f68fc9123334ea01d6397898bd34 Mon Sep 17 00:00:00 2001 From: vibhutikumar <160819926+vibhutikumar07@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:04:32 +0530 Subject: [PATCH 3/6] Update main-build-and-deploy-oss.yml --- .github/workflows/main-build-and-deploy-oss.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-build-and-deploy-oss.yml b/.github/workflows/main-build-and-deploy-oss.yml index 74bfb732..c99c8f12 100644 --- a/.github/workflows/main-build-and-deploy-oss.yml +++ b/.github/workflows/main-build-and-deploy-oss.yml @@ -9,7 +9,7 @@ on: types: [ "released" ] permissions: - contents: read + contents: write jobs: From 3d17c8957578bb587d451150ed4024690f2a9c88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Aug 2026 11:40:28 +0000 Subject: [PATCH 4/6] Update version to 1.10.2 --- cap-notebook/version.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 cap-notebook/version.txt diff --git a/cap-notebook/version.txt b/cap-notebook/version.txt new file mode 100644 index 00000000..5ad2491c --- /dev/null +++ b/cap-notebook/version.txt @@ -0,0 +1 @@ +1.10.2 From 5f64111275922295cfe3a82792c69cd0deffee64 Mon Sep 17 00:00:00 2001 From: PujaDeshmukh17 Date: Mon, 31 Aug 2026 20:47:54 +0530 Subject: [PATCH 5/6] Fix for uploading issue --- .../helper/AttachmentsHandlerUtils.java | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java index f5bc8ee9..b9f71050 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java @@ -194,7 +194,7 @@ private static void processAttachmentPaths( List attachmentPaths) { for (String attachmentPath : attachmentPaths) { String entityPath = buildEntityPath(entity, targetEntity, attachmentPath); - String actualPath = buildActualPath(entity, compositionName, attachmentPath); + String actualPath = buildActualPath(entity, compositionName, targetEntity, attachmentPath); // Only add the mapping if both paths are non-null and the key doesn't already exist // This preserves direct attachment mappings from being overwritten by nested ones @@ -272,8 +272,9 @@ private static String buildEntityPath( // Direct attachment: use parent entity path entityPath = parentEntity.getQualifiedName() + "." + attachmentPart; } else { - // Nested attachment: use target entity path to ensure uniqueness - entityPath = targetEntity.getQualifiedName() + "." + attachmentPart; + // Nested attachment: use attachmentPath as-is — it already encodes the owning entity + // (e.g. "AdminService.Sections.attachments"), ensuring uniqueness at any depth. + entityPath = attachmentPath; } return entityPath; } @@ -284,19 +285,34 @@ private static String buildEntityPath( } private static String buildActualPath( - CdsEntity parentEntity, String compositionPropertyName, String attachmentPath) { + CdsEntity parentEntity, + String compositionPropertyName, + CdsEntity targetEntity, + String attachmentPath) { try { String[] pathParts = attachmentPath.split("\\."); if (pathParts.length >= 3) { - // Get the attachment part (last part) String attachmentPart = pathParts[pathParts.length - 1]; - - // Build the new path using parent entity qualified name + composition property name - return parentEntity.getQualifiedName() - + "." - + compositionPropertyName - + "." - + attachmentPart; + String ownerEntityQN = attachmentPath.substring(0, attachmentPath.lastIndexOf('.')); + + if (ownerEntityQN.equals(targetEntity.getQualifiedName())) { + return parentEntity.getQualifiedName() + + "." + + compositionPropertyName + + "." + + attachmentPart; + } else { + String intermediatePath = findPathToEntity(targetEntity, ownerEntityQN, new HashSet<>()); + if (intermediatePath != null) { + return parentEntity.getQualifiedName() + + "." + + compositionPropertyName + + "." + + intermediatePath + + "." + + attachmentPart; + } + } } } catch (Exception e) { logger.warn(SDMUtils.getErrorMessage("FETCH_ATTACHMENT_COMPOSITION_ERROR"), e.getMessage()); @@ -304,6 +320,27 @@ private static String buildActualPath( return null; } + private static String findPathToEntity( + CdsEntity fromEntity, String toEntityQN, Set visited) { + if (visited.contains(fromEntity.getQualifiedName())) return null; + visited.add(fromEntity.getQualifiedName()); + List comps = fromEntity.compositions().collect(java.util.stream.Collectors.toList()); + for (Object comp : comps) { + com.sap.cds.reflect.CdsElement element = (com.sap.cds.reflect.CdsElement) comp; + if (!element.getType().isAssociation()) continue; + CdsAssociationType assocType = (CdsAssociationType) element.getType(); + CdsEntity target = assocType.getTarget(); + if (toEntityQN.equals(target.getQualifiedName())) { + return element.getName(); + } + String subPath = findPathToEntity(target, toEntityQN, visited); + if (subPath != null) { + return element.getName() + "." + subPath; + } + } + return null; + } + private static List> findNestedAttachments( Map entity, String attachmentKey, String parentKey, String currentParentKey) { List> result = new ArrayList<>(); From 153e19ee665a325a46e7bb3f6c552ae93333e13e Mon Sep 17 00:00:00 2001 From: Rashmi Angadi Date: Mon, 7 Sep 2026 10:50:30 +0530 Subject: [PATCH 6/6] Nexus File Download issue because of uploading status in Saved state --- pom.xml | 2 +- .../SDMReadAttachmentsHandler.java | 2 +- .../common/SDMApplicationHandlerHelper.java | 21 ++++++- .../com/sap/cds/sdm/utilities/SDMUtils.java | 57 +++++++++++++------ 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index ab4efefd..43e0f514 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.10.2 + 1.10.3.1-SNAPSHOT 21 ${java.version} ${java.version} diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java index 241e2853..c203f9a8 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java @@ -133,7 +133,7 @@ public void processBefore(CdsReadEventContext context) throws IOException { return; } setErrorMessagesInCache(context); - if (context.getTarget().getAnnotationValue(SDMConstants.ANNOTATION_IS_MEDIA_DATA, false)) { + if (SDMApplicationHandlerHelper.isMediaEntity(context.getTarget())) { try { // update the uploadStatus of all blank attachments with success this is for existing // attachments diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java b/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java index 5d8d692d..a4b25156 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java @@ -13,14 +13,31 @@ public final class SDMApplicationHandlerHelper { private static final String ANNOTATION_IS_MEDIA_DATA = "_is_media_data"; /** - * Checks if the entity is a media entity. A media entity is an entity that is annotated with the - * annotation "_is_media_data". + * Checks if the entity is a media entity. A media entity is one that carries the "_is_media_data" + * annotation (set by the CAP attachments plugin on DB-backed attachment entities), or — as a + * fallback for service-layer-only draft entities whose annotation may not be propagated — one + * that has both the SDM-specific "objectId" element and the "content" element that is + * characteristic of the sap.attachments.Attachments aspect. * * @param baseEntity The entity to check * @return true if the entity is a media entity, false otherwise */ public static boolean isMediaEntity(CdsStructuredType baseEntity) { boolean isMedia = baseEntity.getAnnotationValue(ANNOTATION_IS_MEDIA_DATA, false); + if (!isMedia) { + // Fallback for service-layer-only entities (e.g. SupplierBidTermValuesServiceEntity + // attachments) whose inline Composition of Attachments does not receive the + // _is_media_data annotation at runtime. Presence of both "objectId" (SDM-specific + // extension) and "content" (core MediaData field) is a reliable structural signal. + isMedia = + baseEntity.findElement("objectId").isPresent() + && baseEntity.findElement("content").isPresent(); + if (isMedia) { + logger.debug( + "Entity {} identified as media entity via structural fallback (objectId + content)", + baseEntity.getQualifiedName()); + } + } logger.debug("Entity {} isMediaEntity: {}", baseEntity.getQualifiedName(), isMedia); return isMedia; } diff --git a/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java b/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java index bbe5b9c1..89572bd5 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java +++ b/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java @@ -398,9 +398,9 @@ public static Map getSecondaryPropertiesWithInvalidDefinition( title = titleAnnotation.get().getValue().toString(); } else { title = element.getName(); /* - * This is in case the user has not specified a title for the column in the cds - * file (which is optional) - */ + * This is in case the user has not specified a title for the column in the cds + * file (which is optional) + */ } invalidProperties.put(key, title); } @@ -570,7 +570,19 @@ private static void retrieveAnnotations(CdsElement cdsElement, AttachmentInfo at } private static List getKeyElementNames(CdsEntity entity) { - return entity.elements().filter(CdsElement::isKey).map(CdsElement::getName).toList(); + return entity + .elements() + .filter(CdsElement::isKey) + .map( + e -> { + String name = e.getName(); + // _drafts entity key columns have an "up__" prefix (e.g. up__bidUUID, + // up__bookItem_ID). + // The CQN where clause uses the bare field names (bidUUID, bookItem_ID). + // Strip the prefix so the contains-check in fetchUPIDFromCQN matches correctly. + return name.startsWith("up__") ? name.substring(4) : name; + }) + .toList(); } /** @@ -586,26 +598,37 @@ public static String fetchUPIDFromCQN(CqnSelect select, CdsEntity parentEntity) String upID = null; ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(select.toString()); + logger.debug( + "fetchUPIDFromCQN - CQN from.ref: {}", root.path("SELECT").path("from").path("ref")); JsonNode refArray = root.path("SELECT").path("from").path("ref"); - JsonNode secondLast = refArray.get(refArray.size() - 2); - JsonNode whereArray; - if (secondLast != null) { - whereArray = secondLast.path("where"); - } else { - whereArray = refArray; + // Get the actual key field names from the parent entity + List keyElementNames = getKeyElementNames(parentEntity); + + // Scan ref nodes from right to left (excluding the last, which is the attachment entity + // itself) to find the first node that has a where clause containing a matching key. + // This handles deep navigation paths like: + // SourcingEvents(id=...) / items(id=...) / terms(id=...) / initialSlice / itemValue / + // attachments + // where intermediate nodes (initialSlice, itemValue) have no where clause. + JsonNode whereArray = null; + for (int r = refArray.size() - 2; r >= 0; r--) { + JsonNode refNode = refArray.get(r); + if (refNode == null) continue; + JsonNode candidate = refNode.path("where"); + if (candidate != null && !candidate.isMissingNode() && candidate.size() > 0) { + whereArray = candidate; + break; + } } - // If where condition is not present or empty, return null (valid scenario for - // select without - // filter) - if (whereArray == null || whereArray.isMissingNode() || whereArray.size() == 0) { + // If no ref node with a where clause was found, return null (valid scenario for + // select without filter) + if (whereArray == null) { + logger.debug("END: fetchUPIDFromCQN - upID: null (no where clause found in ref chain)"); return null; } - // Get the actual key field names from the parent entity - List keyElementNames = getKeyElementNames(parentEntity); - for (int i = 0; i < whereArray.size(); i++) { JsonNode node = whereArray.get(i);