Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ public void updateName(
+ "\nPage: "
+ (parentTitle != null ? parentTitle : "Unknown");

Optional<CdsEntity> attachmentEntity =
context.getModel().findEntity(attachmentCompositionDefinition);
isError =
AttachmentsHandlerUtils.validateFileNames(
context, data, attachmentCompositionName, contextInfo);
context, data, attachmentCompositionName, contextInfo, attachmentEntity);
if (!isError) {
List<String> fileNameWithRestrictedCharacters = new ArrayList<>();
List<String> duplicateFileNameList = new ArrayList<>();
Expand All @@ -114,8 +116,6 @@ public void updateName(
targetEntity);
continue;
}
Optional<CdsEntity> attachmentEntity =
context.getModel().findEntity(attachmentCompositionDefinition);
propertyTitles = SDMUtils.getPropertyTitles(attachmentEntity, attachments.get(0));
secondaryPropertiesWithInvalidDefinitions =
SDMUtils.getSecondaryPropertiesWithInvalidDefinition(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ public void updateName(
+ "\nPage: "
+ (parentTitle != null ? parentTitle : "Unknown");

Optional<CdsEntity> attachmentEntity = Optional.empty();
if (context.getModel() != null) {
attachmentEntity = context.getModel().findEntity(attachmentCompositionDefinition);
}
isError =
AttachmentsHandlerUtils.validateFileNames(
context, data, attachmentCompositionName, contextInfo);
context, data, attachmentCompositionName, contextInfo, attachmentEntity);
if (!isError) {
Optional<CdsEntity> attachmentEntity =
context.getModel().findEntity(attachmentCompositionDefinition);
renameDocument(
attachmentEntity,
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,17 +559,25 @@ private static String extractTitleFromEntity(Object entityObj) {
* @return true if any validation errors are found, false otherwise
*/
public static Boolean validateFileNames(
EventContext context, List<CdsData> data, String composition, String contextInfo) {
EventContext context,
List<CdsData> data,
String composition,
String contextInfo,
Optional<CdsEntity> attachmentEntity) {
Boolean isError = false;
String targetEntity = context.getTarget().getQualifiedName();
String upIdKey = "";
if (attachmentEntity.isPresent()) {
upIdKey = SDMUtils.getUpIdKey(attachmentEntity.get());
}

// Validation for file names
Set<String> whitespaceFilenames =
SDMUtils.FileNameContainsWhitespace(data, composition, targetEntity);
List<String> restrictedFileNames =
SDMUtils.FileNameContainsRestrictedCharaters(data, composition, targetEntity);
Set<String> duplicateFilenames =
SDMUtils.FileNameDuplicateInDrafts(data, composition, targetEntity);
SDMUtils.FileNameDuplicateInDrafts(data, composition, targetEntity, upIdKey);

// Collecting all the errors
if (whitespaceFilenames != null && !whitespaceFilenames.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ public void copyAttachments(CopyAttachmentInput input, boolean isSystemUser) {

// Parse facet to extract parent entity and composition name
String[] facetParts = input.facet().split("\\.");
if (facetParts.length < 3) {
if (facetParts.length < 2) {
throw new IllegalArgumentException(
String.format(SDMConstants.INVALID_FACET_FORMAT_ERROR, input.facet()));
}

String parentEntity = facetParts[0] + "." + facetParts[1]; // Service.Entity
String compositionName = facetParts[2]; // composition name
// The last part is the composition name, everything else is the parent entity
String compositionName = facetParts[facetParts.length - 1];
String parentEntity = input.facet().substring(0, input.facet().lastIndexOf("."));
logger.info("Composition Name: {}, Parent Entity: {}", compositionName, parentEntity);

var copyContext = AttachmentCopyEventContext.create();
copyContext.setUpId(input.upId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void processEntities(AttachmentCreateEventContext eventContext)

Map<String, Object> attachmentIds = eventContext.getAttachmentIds();
CdsEntity attachmentDraftEntity = getAttachmentDraftEntity(eventContext);
String upIdKey = getUpIdKey(attachmentDraftEntity);
String upIdKey = SDMUtils.getUpIdKey(attachmentDraftEntity);
String upID = (String) attachmentIds.get(upIdKey);

Result result =
Expand All @@ -202,20 +202,6 @@ private CdsEntity getAttachmentDraftEntity(AttachmentCreateEventContext eventCon
() -> new ServiceException(SDMConstants.DRAFT_NOT_FOUND));
}

private String getUpIdKey(CdsEntity attachmentDraftEntity) {
String upIdKey = "";
Optional<CdsElement> upAssociation = attachmentDraftEntity.findAssociation("up_");
if (upAssociation.isPresent()) {
CdsElement association = upAssociation.get();
// get association type
CdsAssociationType associationType = association.getType();
// get the refs of the association
List<String> fkElements = associationType.refs().map(ref -> "up__" + ref.path()).toList();
upIdKey = fkElements.get(0);
}
return upIdKey;
}

private void checkAttachmentConstraints(
AttachmentCreateEventContext eventContext,
CdsEntity attachmentDraftEntity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ private void revertLinksForComposition(
CdsEntity draftEntity = model.findEntity(draftEntityName).get();
CdsEntity activeEntity = model.findEntity(attachmentCompositionDefinition).get();

String upIdKey = getUpIdKey(draftEntity);
final String upIdKey = SDMUtils.getUpIdKey(draftEntity);
if (upIdKey == null || upIdKey.isEmpty()) {
return;
}
String parentKeyName = upIdKey.replaceFirst("^up__", "");
Object parentId = parentKeys.get(parentKeyName);

Expand Down Expand Up @@ -330,7 +333,9 @@ private void createLink(EventContext context) throws IOException {
cdsModel.findEntity(context.getTarget().getQualifiedName() + "_drafts");

String upIdKey =
attachmentDraftEntity.isPresent() ? getUpIdKey(attachmentDraftEntity.get()) : "up__ID";
attachmentDraftEntity.isPresent()
? SDMUtils.getUpIdKey(attachmentDraftEntity.get())
: "up__ID";
CqnSelect select = (CqnSelect) context.get("cqn");
// Get parent entity to extract its key names dynamically
String parentEntityName = null;
Expand Down Expand Up @@ -432,20 +437,6 @@ private void editLink(EventContext context) throws IOException {
context.setCompleted();
}

private String getUpIdKey(CdsEntity attachmentDraftEntity) {
String upIdKey = "";
Optional<CdsElement> upAssociation = attachmentDraftEntity.findAssociation("up_");
if (upAssociation.isPresent()) {
CdsElement association = upAssociation.get();
// get association type
CdsAssociationType associationType = association.getType();
// get the refs of the association
List<String> fkElements = associationType.refs().map(ref -> "up__" + ref.path()).toList();
upIdKey = fkElements.get(0);
}
return upIdKey;
}

/**
* Retrieves the key element names from a CdsEntity. This method extracts the names of all key
* fields defined in the entity, allowing for dynamic key field handling instead of hardcoding
Expand Down
35 changes: 33 additions & 2 deletions sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sap.cds.CdsData;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.sdm.caching.CacheConfig;
Expand All @@ -26,8 +27,11 @@
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SDMUtils {
private static final Logger logger = LoggerFactory.getLogger(CacheConfig.class);

private SDMUtils() {
// Doesn't do anything
Expand All @@ -54,7 +58,7 @@ public static Set<String> FileNameContainsWhitespace(
}

public static Set<String> FileNameDuplicateInDrafts(
List<CdsData> data, String composition, String targetEntity) {
List<CdsData> data, String composition, String targetEntity, String upIdKey) {
Set<String> uniqueFilenames = new HashSet<>();
Set<String> duplicateFilenames = new HashSet<>();
for (Map<String, Object> entity : data) {
Expand All @@ -67,7 +71,10 @@ public static Set<String> FileNameDuplicateInDrafts(
String filenameInRequest = (String) attachment.get("fileName");
if (filenameInRequest != null && !filenameInRequest.isBlank()) {
String repositoryInRequest = (String) attachment.get("repositoryId");
String fileRepositorySpecific = filenameInRequest + "#" + repositoryInRequest;
String upId = (String) attachment.get(upIdKey);
String fileRepositorySpecific =
filenameInRequest + "#" + repositoryInRequest + "#" + upId;
logger.info("Filename key check : " + fileRepositorySpecific);
if (!uniqueFilenames.add(fileRepositorySpecific)) {
duplicateFilenames.add(filenameInRequest);
}
Expand Down Expand Up @@ -384,6 +391,30 @@ public static boolean isRelatedEntity(CdsEntity attachmentEntity, CdsEntity cdsE
&& !attachmentQualifiedName.equals(cdsEntity.getQualifiedName());
}

public static String getUpIdKey(CdsEntity attachmentDraftEntity) {
String upIdKey = "";
Optional<CdsElement> upAssociation = attachmentDraftEntity.findAssociation("up_");
if (upAssociation.isPresent()) {
CdsElement association = upAssociation.get();
// get association type
CdsAssociationType associationType = association.getType();
// get the refs of the association
List<String> fkElements = associationType.refs().map(ref -> "up__" + ref.path()).toList();
if (!fkElements.isEmpty()) {
upIdKey = fkElements.get(0);
}
}
// Fallback: if no association found, try to find element starting with "up__"
if (upIdKey.isEmpty()) {
Optional<CdsElement> upElement =
attachmentDraftEntity.elements().filter(e -> e.getName().startsWith("up__")).findFirst();
if (upElement.isPresent()) {
upIdKey = upElement.get().getName();
}
}
return upIdKey;
}

private static void processCompositions(
CdsEntity cdsEntity, AttachmentInfo attachmentInfo, CdsEntity attachmentEntity) {
List<CdsElement> compositions = cdsEntity.compositions().toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ public void testUpdateNameWithDuplicateFilenames() throws IOException {
CdsEntity targetEntity = mock(CdsEntity.class);
when(targetEntity.getQualifiedName()).thenReturn("TestEntity");
when(context.getTarget()).thenReturn(targetEntity);

// Mock the attachment entity
CdsEntity attachmentEntity = mock(CdsEntity.class);
when(context.getModel().findEntity("compositionDefinition"))
.thenReturn(Optional.of(attachmentEntity));

// Make validateFileName execute its real implementation, and stub helper methods
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameContainsWhitespace(anyList(), anyString(), anyString()))
Expand All @@ -144,16 +150,19 @@ public void testUpdateNameWithDuplicateFilenames() throws IOException {
() ->
SDMUtils.FileNameContainsRestrictedCharaters(anyList(), anyString(), anyString()))
.thenReturn(Collections.emptyList());
sdmUtilsMockedStatic.when(() -> SDMUtils.getUpIdKey(attachmentEntity)).thenReturn("upId");
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameDuplicateInDrafts(data, "compositionName", "TestEntity"))
.when(
() ->
SDMUtils.FileNameDuplicateInDrafts(data, "compositionName", "TestEntity", "upId"))
.thenReturn(duplicateFilenames);
try (MockedStatic<AttachmentsHandlerUtils> attachmentUtilsMockedStatic =
mockStatic(AttachmentsHandlerUtils.class)) {
attachmentUtilsMockedStatic
.when(
() ->
AttachmentsHandlerUtils.validateFileNames(
any(), anyList(), anyString(), anyString()))
any(), anyList(), anyString(), anyString(), any()))
.thenCallRealMethod();

// Act
Expand All @@ -179,7 +188,7 @@ public void testUpdateNameWithEmptyData() throws IOException {
// Arrange
List<CdsData> data = new ArrayList<>();
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameDuplicateInDrafts(data, "compositionName", "entity"))
.when(() -> SDMUtils.FileNameDuplicateInDrafts(data, "compositionName", "entity", "upId"))
.thenReturn(Collections.emptySet());

// Act
Expand Down Expand Up @@ -216,7 +225,7 @@ public void testUpdateNameWithNoAttachments() throws IOException {

// Mock utility methods
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameDuplicateInDrafts(data, "compositionName", "entity"))
.when(() -> SDMUtils.FileNameDuplicateInDrafts(data, "compositionName", "entity", "upId"))
.thenReturn(Collections.emptySet());

// Act
Expand Down Expand Up @@ -476,7 +485,7 @@ public void testUpdateNameWithEmptyFilename() throws IOException {
.when(
() ->
SDMUtils.FileNameDuplicateInDrafts(
data, "compositionName", "some.qualified.Name"))
data, "compositionName", "some.qualified.Name", "upId"))
.thenReturn(new HashSet<>());

// Mock AttachmentsHandlerUtils.fetchAttachments to return the attachment with null filename
Expand All @@ -492,7 +501,7 @@ public void testUpdateNameWithEmptyFilename() throws IOException {
.when(
() ->
AttachmentsHandlerUtils.validateFileNames(
any(), anyList(), anyString(), anyString()))
any(), anyList(), anyString(), anyString(), any()))
.thenCallRealMethod();

// Mock attachment entity
Expand Down Expand Up @@ -555,7 +564,10 @@ public void testUpdateNameWithEmptyFilename() throws IOException {
.when(() -> SDMUtils.FileNameContainsWhitespace(anyList(), anyString(), anyString()))
.thenCallRealMethod();
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameDuplicateInDrafts(anyList(), anyString(), anyString()))
.when(
() ->
SDMUtils.FileNameDuplicateInDrafts(
anyList(), anyString(), anyString(), anyString()))
.thenReturn(new HashSet<>());

// Act
Expand Down Expand Up @@ -614,7 +626,10 @@ public void testUpdateNameWithRestrictedCharacters() throws IOException {
.when(() -> SDMUtils.FileNameContainsWhitespace(anyList(), anyString(), anyString()))
.thenReturn(Collections.emptySet());
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameDuplicateInDrafts(anyList(), anyString(), anyString()))
.when(
() ->
SDMUtils.FileNameDuplicateInDrafts(
anyList(), anyString(), anyString(), anyString()))
.thenReturn(Collections.emptySet());
sdmUtilsMockedStatic
.when(
Expand All @@ -635,7 +650,7 @@ public void testUpdateNameWithRestrictedCharacters() throws IOException {
.when(
() ->
AttachmentsHandlerUtils.validateFileNames(
any(), anyList(), anyString(), anyString()))
any(), anyList(), anyString(), anyString(), any()))
.thenCallRealMethod();

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public void testRenameWithDuplicateFilenames() throws IOException {
.when(
() ->
AttachmentsHandlerUtils.validateFileNames(
any(), anyList(), anyString(), anyString()))
any(), anyList(), anyString(), anyString(), any()))
.thenCallRealMethod();

// Mock SDMUtils helper methods to ensure validation works correctly
Expand All @@ -185,7 +185,10 @@ public void testRenameWithDuplicateFilenames() throws IOException {
Set<String> duplicateFiles = new HashSet<>();
duplicateFiles.add("file1.txt");
sdmUtilsMockedStatic
.when(() -> SDMUtils.FileNameDuplicateInDrafts(anyList(), anyString(), anyString()))
.when(
() ->
SDMUtils.FileNameDuplicateInDrafts(
anyList(), anyString(), anyString(), anyString()))
.thenReturn(duplicateFiles);

// Call the method under test; validateFileNames will detect duplicates and call
Expand Down Expand Up @@ -411,7 +414,7 @@ public void testRenameWithNoSDMRoles() throws IOException {
.when(
() ->
SDMUtils.FileNameDuplicateInDrafts(
any(List.class), eq("compositionName"), anyString()))
any(List.class), eq("compositionName"), anyString(), anyString()))
.thenReturn(Collections.emptySet());

sdmUtilsMock
Expand Down
Loading
Loading