Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</developers>

<properties>
<revision>1.10.2</revision>
<revision>1.10.3.1-SNAPSHOT</revision>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>true</code> if the entity is a media entity, <code>false</code> 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;
}
Expand Down
57 changes: 40 additions & 17 deletions sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,9 @@ public static Map<String, String> 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);
}
Expand Down Expand Up @@ -570,7 +570,19 @@ private static void retrieveAnnotations(CdsElement cdsElement, AttachmentInfo at
}

private static List<String> 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();
}

/**
Expand All @@ -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<String> 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<String> keyElementNames = getKeyElementNames(parentEntity);

for (int i = 0; i < whereArray.size(); i++) {
JsonNode node = whereArray.get(i);

Expand Down
Loading