Skip to content

Commit d5bd4e5

Browse files
authored
Merge pull request #424 from cap-java/moveAttachmentsSupportForActiveEntity
Supoort of moveAttachments for Active entity & Integration Tests Fix
2 parents 589d454 + 3f2c637 commit d5bd4e5

12 files changed

Lines changed: 509 additions & 570 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</developers>
2424

2525
<properties>
26-
<revision>1.7.1-SNAPSHOT</revision>
26+
<revision>1.0.0-RC1</revision>
2727
<java.version>17</java.version>
2828
<maven.compiler.source>${java.version}</maven.compiler.source>
2929
<maven.compiler.target>${java.version}</maven.compiler.target>

sdm/src/main/java/com/sap/cds/sdm/model/ValidatedAttachmentData.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ public class ValidatedAttachmentData {
2020
private final List<List<String>> movedAttachmentsMetadata;
2121
private final List<CmisDocument> populatedDocuments;
2222
private final CmisDocument sourceCmisDocument;
23+
private final String createdBy;
24+
private final java.time.Instant creationDate;
25+
private final String lastModifiedBy;
26+
private final java.time.Instant lastModificationDate;
2327

2428
public ValidatedAttachmentData(
2529
String objectId,
@@ -33,7 +37,11 @@ public ValidatedAttachmentData(
3337
List<String> successfulObjectIds,
3438
List<List<String>> movedAttachmentsMetadata,
3539
List<CmisDocument> populatedDocuments,
36-
CmisDocument sourceCmisDocument) {
40+
CmisDocument sourceCmisDocument,
41+
String createdBy,
42+
java.time.Instant creationDate,
43+
String lastModifiedBy,
44+
java.time.Instant lastModificationDate) {
3745
this.objectId = objectId;
3846
this.fileName = fileName;
3947
this.mimeType = mimeType;
@@ -46,6 +54,10 @@ public ValidatedAttachmentData(
4654
this.movedAttachmentsMetadata = movedAttachmentsMetadata;
4755
this.populatedDocuments = populatedDocuments;
4856
this.sourceCmisDocument = sourceCmisDocument;
57+
this.createdBy = createdBy;
58+
this.creationDate = creationDate;
59+
this.lastModifiedBy = lastModifiedBy;
60+
this.lastModificationDate = lastModificationDate;
4961
}
5062

5163
public String getObjectId() {
@@ -119,4 +131,20 @@ public void addPopulatedDocument(CmisDocument document) {
119131
public CmisDocument getSourceCmisDocument() {
120132
return sourceCmisDocument;
121133
}
134+
135+
public String getCreatedBy() {
136+
return createdBy;
137+
}
138+
139+
public java.time.Instant getCreationDate() {
140+
return creationDate;
141+
}
142+
143+
public String getLastModifiedBy() {
144+
return lastModifiedBy;
145+
}
146+
147+
public java.time.Instant getLastModificationDate() {
148+
return lastModificationDate;
149+
}
122150
}

sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMCustomServiceHandler.java

Lines changed: 204 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,19 @@ private void processSingleAttachmentMove(AttachmentMoveContext moveContext) {
10631063
String movedObjectId = succinctProperties.optString("cmis:objectId");
10641064
String objectTypeId = succinctProperties.optString("cmis:objectTypeId");
10651065

1066+
// Extract managed fields from SDM response to retain original timestamps and users
1067+
String createdBy = succinctProperties.optString("cmis:createdBy", null);
1068+
Instant creationDate = null;
1069+
if (succinctProperties.has("cmis:creationDate")) {
1070+
creationDate = Instant.ofEpochMilli(succinctProperties.getLong("cmis:creationDate"));
1071+
}
1072+
String lastModifiedBy = succinctProperties.optString("cmis:lastModifiedBy", null);
1073+
Instant lastModificationDate = null;
1074+
if (succinctProperties.has("cmis:lastModificationDate")) {
1075+
lastModificationDate =
1076+
Instant.ofEpochMilli(succinctProperties.getLong("cmis:lastModificationDate"));
1077+
}
1078+
10661079
// Determine attachment type based on cmis:objectTypeId from SDM response
10671080
// Link attachments: "sap:link" -> "sap-icon://internet-browser"
10681081
// Document attachments: "cmis:document" -> "sap-icon://document"
@@ -1153,7 +1166,11 @@ private void processSingleAttachmentMove(AttachmentMoveContext moveContext) {
11531166
moveContext.getProcessingResults().getSuccessfulObjectIds(),
11541167
moveContext.getProcessingResults().getMovedAttachmentsMetadata(),
11551168
moveContext.getProcessingResults().getPopulatedDocuments(),
1156-
cmisDocument);
1169+
cmisDocument,
1170+
createdBy,
1171+
creationDate,
1172+
lastModifiedBy,
1173+
lastModificationDate);
11571174
processValidatedAttachment(validatedData);
11581175
}
11591176

@@ -1221,7 +1238,13 @@ private void processValidatedAttachment(ValidatedAttachmentData data) {
12211238
data.getFileName(),
12221239
data.getMimeType(),
12231240
data.getDescription(),
1224-
data.getMovedObjectId()));
1241+
data.getMovedObjectId(),
1242+
data.getCreatedBy() != null ? data.getCreatedBy() : "",
1243+
data.getCreationDate() != null ? data.getCreationDate().toString() : "",
1244+
data.getLastModifiedBy() != null ? data.getLastModifiedBy() : "",
1245+
data.getLastModificationDate() != null
1246+
? data.getLastModificationDate().toString()
1247+
: ""));
12251248
data.addPopulatedDocument(populatedDocument);
12261249
}
12271250

@@ -1519,96 +1542,195 @@ private String resolveUpIdKey(EventContext context, String parentEntity, String
15191542
* @param data encapsulated draft entry creation data
15201543
*/
15211544
private void createDraftEntriesForMove(DraftEntryMoveData data) {
1522-
15231545
for (int i = 0; i < data.getMovedAttachmentsMetadata().size(); i++) {
15241546
List<String> attachmentMetadata = data.getMovedAttachmentsMetadata().get(i);
15251547
CmisDocument cmisDocument = data.getPopulatedDocuments().get(i);
1526-
Map<String, Object> updatedFields = new HashMap<>();
15271548

1528-
String fileName = attachmentMetadata.get(0);
1529-
String mimeType = attachmentMetadata.get(1);
1530-
String description = attachmentMetadata.get(2);
1531-
String newObjectId = attachmentMetadata.get(3);
1549+
Map<String, Object> updatedFields =
1550+
buildUpdatedFieldsForMove(attachmentMetadata, cmisDocument, data);
15321551

1533-
updatedFields.put(OBJECT_ID_KEY, newObjectId);
1534-
updatedFields.put("repositoryId", data.getRepositoryId());
1535-
updatedFields.put("folderId", data.getFolderId());
1536-
updatedFields.put("status", "Clean");
1537-
updatedFields.put("mimeType", mimeType);
1538-
updatedFields.put("type", cmisDocument.getType());
1539-
updatedFields.put("fileName", fileName);
1540-
updatedFields.put("note", description);
1541-
updatedFields.put("HasDraftEntity", false);
1542-
updatedFields.put("HasActiveEntity", false);
1543-
updatedFields.put("linkUrl", cmisDocument.getUrl());
1544-
updatedFields.put(
1545-
"contentId",
1546-
newObjectId
1547-
+ ":"
1548-
+ data.getFolderId()
1549-
+ ":"
1550-
+ data.getParentEntity()
1551-
+ "."
1552-
+ data.getCompositionName()
1553-
+ ":"
1554-
+ mimeType);
1555-
updatedFields.put(data.getUpIdKey(), data.getUpID());
1552+
performDraftInsertWithRetry(updatedFields, data);
1553+
}
1554+
}
15561555

1557-
// Include secondary properties from moved attachment
1558-
// Properties are already filtered and validated in processValidatedAttachment()
1559-
// to only include those annotated with @SDM.Attachments.AdditionalProperty
1560-
// and present in valid secondary properties list
1561-
if (cmisDocument.getSecondaryProperties() != null) {
1562-
logger.info(
1563-
"Adding {} secondary properties to DB insert for attachment {}: {}",
1564-
cmisDocument.getSecondaryProperties().size(),
1565-
newObjectId,
1566-
cmisDocument.getSecondaryProperties());
1567-
updatedFields.putAll(cmisDocument.getSecondaryProperties());
1568-
} else {
1569-
logger.warn("No secondary properties to add for attachment {}", newObjectId);
1570-
}
1556+
/**
1557+
* Builds the complete map of fields to be inserted for a moved attachment.
1558+
*
1559+
* @param attachmentMetadata metadata list from SDM response
1560+
* @param cmisDocument the CMIS document with type and URL
1561+
* @param data the draft entry move data
1562+
* @return map of fields ready for database insertion
1563+
*/
1564+
private Map<String, Object> buildUpdatedFieldsForMove(
1565+
List<String> attachmentMetadata, CmisDocument cmisDocument, DraftEntryMoveData data) {
15711566

1572-
logger.info(
1573-
"Final DB insert map for attachment {} contains {} fields: {}",
1574-
newObjectId,
1575-
updatedFields.size(),
1576-
updatedFields.keySet());
1567+
String fileName = attachmentMetadata.get(0);
1568+
String mimeType = attachmentMetadata.get(1);
1569+
String description = attachmentMetadata.get(2);
1570+
String newObjectId = attachmentMetadata.get(3);
15771571

1578-
String baseKeyField =
1579-
data.getUpIdKey() != null ? data.getUpIdKey().replace("up__", "") : "ID";
1580-
var insert =
1581-
Insert.into(
1582-
data.getParentEntity(),
1583-
e ->
1584-
e.filter(e.get(baseKeyField).eq(data.getUpID()))
1585-
.to(data.getCompositionName()))
1586-
.entry(updatedFields);
1572+
Map<String, Object> updatedFields =
1573+
buildBasicFields(newObjectId, fileName, mimeType, description, cmisDocument, data);
15871574

1588-
DraftService matchingService =
1589-
draftService.stream()
1590-
.filter(ds -> data.getParentEntity().contains(ds.getName()))
1591-
.findFirst()
1592-
.orElse(null);
1575+
addManagedFieldsForMove(updatedFields, attachmentMetadata);
1576+
addSecondaryPropertiesForMove(updatedFields, cmisDocument, newObjectId);
15931577

1594-
if (matchingService != null) {
1595-
// Wrap DB insert with retry logic to handle transient DB failures
1596-
try {
1597-
Flowable.fromCallable(
1598-
() -> {
1599-
matchingService.newDraft(insert);
1600-
return true;
1601-
})
1602-
.retryWhen(com.sap.cds.sdm.service.RetryUtils.retryLogic(5)) // Retry up to 5 times
1603-
.blockingFirst();
1604-
} catch (Exception e) {
1605-
throw new ServiceException(
1606-
"Failed to insert attachment entry in DB after retries: " + e.getMessage(), e);
1607-
}
1608-
} else {
1609-
throw new ServiceException(
1610-
"No suitable service found for entity: " + data.getParentEntity());
1611-
}
1578+
logger.info(
1579+
"Final DB insert map for attachment {} contains {} fields: {}",
1580+
newObjectId,
1581+
updatedFields.size(),
1582+
updatedFields.keySet());
1583+
1584+
return updatedFields;
1585+
}
1586+
1587+
/**
1588+
* Builds the basic field map with core attachment properties.
1589+
*
1590+
* @param newObjectId the new object ID
1591+
* @param fileName the file name
1592+
* @param mimeType the MIME type
1593+
* @param description the description
1594+
* @param cmisDocument the CMIS document
1595+
* @param data the draft entry move data
1596+
* @return map with basic fields
1597+
*/
1598+
private Map<String, Object> buildBasicFields(
1599+
String newObjectId,
1600+
String fileName,
1601+
String mimeType,
1602+
String description,
1603+
CmisDocument cmisDocument,
1604+
DraftEntryMoveData data) {
1605+
1606+
Map<String, Object> fields = new HashMap<>();
1607+
fields.put(OBJECT_ID_KEY, newObjectId);
1608+
fields.put("repositoryId", data.getRepositoryId());
1609+
fields.put("folderId", data.getFolderId());
1610+
fields.put("status", "Clean");
1611+
fields.put("mimeType", mimeType);
1612+
fields.put("type", cmisDocument.getType());
1613+
fields.put("fileName", fileName);
1614+
fields.put("note", description);
1615+
fields.put("HasDraftEntity", false);
1616+
fields.put("HasActiveEntity", false);
1617+
fields.put("IsActiveEntity", true);
1618+
fields.put("linkUrl", cmisDocument.getUrl());
1619+
fields.put(
1620+
"contentId",
1621+
newObjectId
1622+
+ ":"
1623+
+ data.getFolderId()
1624+
+ ":"
1625+
+ data.getParentEntity()
1626+
+ "."
1627+
+ data.getCompositionName()
1628+
+ ":"
1629+
+ mimeType);
1630+
fields.put(data.getUpIdKey(), data.getUpID());
1631+
1632+
return fields;
1633+
}
1634+
1635+
/**
1636+
* Adds managed fields (createdBy, createdAt, modifiedBy, modifiedAt) to the fields map.
1637+
*
1638+
* @param fields the fields map to update
1639+
* @param attachmentMetadata the metadata list containing managed field values
1640+
*/
1641+
private void addManagedFieldsForMove(
1642+
Map<String, Object> fields, List<String> attachmentMetadata) {
1643+
1644+
String createdBy = attachmentMetadata.size() > 4 ? attachmentMetadata.get(4) : null;
1645+
String creationDate = attachmentMetadata.size() > 5 ? attachmentMetadata.get(5) : null;
1646+
String lastModifiedBy = attachmentMetadata.size() > 6 ? attachmentMetadata.get(6) : null;
1647+
String lastModificationDate = attachmentMetadata.size() > 7 ? attachmentMetadata.get(7) : null;
1648+
1649+
addFieldIfPresent(fields, "createdBy", createdBy);
1650+
addInstantFieldIfPresent(fields, "createdAt", creationDate);
1651+
addFieldIfPresent(fields, "modifiedBy", lastModifiedBy);
1652+
addInstantFieldIfPresent(fields, "modifiedAt", lastModificationDate);
1653+
}
1654+
1655+
/**
1656+
* Adds a field to the map if the value is present and not empty.
1657+
*
1658+
* @param fields the fields map to update
1659+
* @param fieldName the field name
1660+
* @param value the field value
1661+
*/
1662+
private void addFieldIfPresent(Map<String, Object> fields, String fieldName, String value) {
1663+
if (value != null && !value.isEmpty()) {
1664+
fields.put(fieldName, value);
1665+
}
1666+
}
1667+
1668+
/**
1669+
* Adds an Instant field to the map if the value is present and not empty.
1670+
*
1671+
* @param fields the fields map to update
1672+
* @param fieldName the field name
1673+
* @param value the string representation of the Instant
1674+
*/
1675+
private void addInstantFieldIfPresent(
1676+
Map<String, Object> fields, String fieldName, String value) {
1677+
if (value != null && !value.isEmpty()) {
1678+
fields.put(fieldName, java.time.Instant.parse(value));
1679+
}
1680+
}
1681+
1682+
/**
1683+
* Adds secondary properties from the CMIS document to the fields map.
1684+
*
1685+
* @param fields the fields map to update
1686+
* @param cmisDocument the CMIS document with secondary properties
1687+
* @param objectId the object ID for logging
1688+
*/
1689+
private void addSecondaryPropertiesForMove(
1690+
Map<String, Object> fields, CmisDocument cmisDocument, String objectId) {
1691+
1692+
if (cmisDocument.getSecondaryProperties() != null) {
1693+
logger.info(
1694+
"Adding {} secondary properties to DB insert for attachment {}: {}",
1695+
cmisDocument.getSecondaryProperties().size(),
1696+
objectId,
1697+
cmisDocument.getSecondaryProperties());
1698+
fields.putAll(cmisDocument.getSecondaryProperties());
1699+
} else {
1700+
logger.warn("No secondary properties to add for attachment {}", objectId);
1701+
}
1702+
}
1703+
1704+
/**
1705+
* Performs database insert with retry logic for the given fields.
1706+
*
1707+
* @param updatedFields the fields to insert
1708+
* @param data the draft entry move data containing entity information
1709+
* @throws ServiceException if insert fails after retries
1710+
*/
1711+
private void performDraftInsertWithRetry(
1712+
Map<String, Object> updatedFields, DraftEntryMoveData data) {
1713+
1714+
String baseKeyField = data.getUpIdKey() != null ? data.getUpIdKey().replace("up__", "") : "ID";
1715+
var insert =
1716+
Insert.into(
1717+
data.getParentEntity(),
1718+
e -> e.filter(e.get(baseKeyField).eq(data.getUpID())).to(data.getCompositionName()))
1719+
.entry(updatedFields);
1720+
1721+
// Insert directly into active entity (not draft) using persistenceService
1722+
// Wrap DB insert with retry logic to handle transient DB failures
1723+
try {
1724+
Flowable.fromCallable(
1725+
() -> {
1726+
persistenceService.run(insert);
1727+
return true;
1728+
})
1729+
.retryWhen(com.sap.cds.sdm.service.RetryUtils.retryLogic(5)) // Retry up to 5 times
1730+
.blockingFirst();
1731+
} catch (Exception e) {
1732+
throw new ServiceException(
1733+
"Failed to insert attachment entry in DB after retries: " + e.getMessage(), e);
16121734
}
16131735
}
16141736

0 commit comments

Comments
 (0)