@@ -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