Skip to content

Commit 6587dd5

Browse files
authored
Merge branch 'develop' into changeLogSupport
2 parents 085da9a + 43003a4 commit 6587dd5

32 files changed

Lines changed: 9232 additions & 96 deletions

.github/workflows/internalArticatory.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ env:
77

88
on:
99
workflow_dispatch:
10+
inputs:
11+
deploy_branch:
12+
description: 'Specify the branch foe which snapshot required'
13+
required: true
1014
jobs:
1115
build-and-deploy-artifactory:
1216
runs-on: ubuntu-latest
@@ -15,6 +19,8 @@ jobs:
1519
steps:
1620
- name: Checkout
1721
uses: actions/checkout@v4
22+
with:
23+
ref: ${{ github.event.inputs.deploy_branch }}
1824

1925
- name: Set up Java ${{ env.JAVA_VERSION }}
2026
uses: actions/setup-java@v4

README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei
2020
- Copy attachments: Provides the capability to copy attachments from one entity to another entity.
2121
- Link as attachments: Provides the capability to support link or URL as attachments.
2222
- Edit Link-type attachments: Provides the capability to update URL of link-type attachments.
23+
- Move attachments: Provides the capability to move attachments from one entity to another entity.
2324
- Localization of error messages and UI fields: Provides the capability to have the UI fields and error messages translated to the local language of the leading application.
2425
## Table of Contents
2526

@@ -33,6 +34,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei
3334
- [Support for Multiple attachment facets](#support-for-multiple-attachment-facets)
3435
- [Support for Technical user](#support-for-technical-user)
3536
- [Support for Copy attachments](#support-for-copy-attachments)
37+
- [Support for Move attachments](#support-for-move-attachments)
3638
- [Support for Link type attachments](#support-for-link-type-attachments)
3739
- [Support for Edit of Link type attachments](#support-for-edit-of-link-type-attachments)
3840
- [Support for Localization](#support-for-localization)
@@ -568,6 +570,143 @@ This plugin provides capability to copy attachments from one entity to another.
568570
}
569571
```
570572

573+
## Support for move attachments
574+
575+
This plugin provides capability to move attachments from one entity to another entity. This capability will move attachments metadata on CAP as well as actual content on the SAP Document Management service repository. The move operation is performed in parallel for optimal performance and includes comprehensive error handling and rollback mechanisms.
576+
577+
### Key Features
578+
579+
- **Parallel Processing**: Move operations are executed in parallel using a thread pool for improved performance.
580+
- **Custom Properties Support**: Preserves and validates custom properties during the move.
581+
- **Automatic Rollback**: If database updates fail after a successful SDM move, the operation is automatically rolled back.
582+
- **Comprehensive Error Handling**: Returns detailed failure information for each attachment that fails to move.
583+
- **Folder Management**: Automatically creates target folders if they don't exist.
584+
585+
### Usage Methods
586+
587+
1. **A helper method to move attachments from one entity to another**
588+
589+
The `AttachmentService` instance can be used to call `moveAttachments` method. This method expects an object of `MoveAttachmentInput` which requires the source folder ID, target entity's ID (`up__Id`), the `attachments facet name` and the `list of objectIds` corresponding to attachments that are to be moved.
590+
591+
Example usage:
592+
```java
593+
String sourceFolderId = "source-folder-id";
594+
String up__ID = "123";
595+
List<String> objectIds = ["abc", "xyz"];
596+
String facet = "AdminService.Books.attachments"
597+
boolean isSystemUser = false;
598+
599+
var moveEventInput = new MoveAttachmentInput(
600+
sourceFolderId,
601+
up__ID,
602+
facet,
603+
objectIds
604+
);
605+
606+
List<Map<String, String>> failedAttachments =
607+
attachmentService.moveAttachments(moveEventInput, isSystemUser);
608+
609+
// Check for failures
610+
if (!failedAttachments.isEmpty()) {
611+
for (Map<String, String> failure : failedAttachments) {
612+
String objectId = failure.get("objectId");
613+
String reason = failure.get("failureReason");
614+
// Handle failure
615+
}
616+
}
617+
```
618+
619+
2. **OData API to move attachments from one entity to another**
620+
621+
You can also use an OData API call to trigger the move operation.
622+
`AttachmentsService` endpoint URL can be used with suffix `/<Service_name>.moveAttachments`. This request expects the following request body:
623+
```json
624+
{
625+
"sourceFolderId": "<source-folder-id>",
626+
"up__ID": "<target-up-id>",
627+
"facet": "<Service_Name>.<Entity_Name>.attachments",
628+
"objectIds": ["abc", "xyz"]
629+
}
630+
```
631+
632+
Example usage:
633+
```
634+
HTTP Method: POST
635+
Request URL:
636+
<app_url>/odata/v4/<Service_Name>/<Entity_Name>(ID=<up__ID>,IsActiveEntity=false)/attachments/<Service_name>.moveAttachments
637+
Request Body:
638+
{
639+
"sourceFolderId": "<source-folder-id>",
640+
"up__ID": "<target-up-id>",
641+
"facet": "AdminService.Books.attachments",
642+
"objectIds": ["abc", "xyz"]
643+
}
644+
```
645+
646+
Note: The `facet` parameter should be the fully qualified name of the target attachment composition (e.g., `AdminService.Books.attachments`).
647+
648+
### Optional Parameters
649+
650+
When moving attachments, you can provide optional source facet information for proper cleanup:
651+
652+
```java
653+
var moveEventInput = new MoveAttachmentInput(
654+
sourceFolderId,
655+
up__ID,
656+
facet,
657+
objectIds,
658+
sourceFacet // Optional: Full facet path, e.g., "AdminService.Authors.attachments"
659+
);
660+
```
661+
662+
If `sourceFacet` is provided, the source entity metadata will be properly cleaned up after the move. If omitted, attachments are moved but source metadata cleanup is skipped.
663+
664+
For OData API calls, you can include the optional `sourceFacet` parameter in the request body:
665+
```json
666+
{
667+
"sourceFolderId": "<source-folder-id>",
668+
"up__ID": "<target-up-id>",
669+
"facet": "AdminService.Books.attachments",
670+
"objectIds": ["abc", "xyz"],
671+
"sourceFacet": "AdminService.Authors.attachments"
672+
}
673+
```
674+
675+
### Response Format
676+
677+
The move operation returns a list of failed attachments with detailed failure reasons:
678+
679+
```json
680+
[
681+
{
682+
"objectId": "abc",
683+
"failureReason": "Attachment abc already exists in Target entity"
684+
},
685+
{
686+
"objectId": "xyz",
687+
"failureReason": "Invalid custom properties: customProp1, customProp2. These properties are not supported in the target entity."
688+
}
689+
]
690+
```
691+
692+
### Common Failure Scenarios
693+
694+
- **MaxCount Exceeded**: Target entity has reached its maximum allowed attachments.
695+
- **Invalid Custom Properties**: Attachment has custom properties not supported by the target entity.
696+
- **Permission Denied**: User lacks authorization to move the attachment.
697+
- **Duplicate File**: File with the same name already exists in the target folder.
698+
- **Database Update Failed**: Move succeeded in SDM but database update failed (automatic rollback occurs).
699+
- **Source Not Found**: Source attachment doesn't exist in SDM.
700+
701+
### Best Practices
702+
703+
1. **Always check the returned list of failed attachments** to inform users about partial failures.
704+
2. **Validate maxCount constraints** before initiating large move operations.
705+
3. **Ensure custom properties compatibility** between source and target entities.
706+
5. **Handle rollback scenarios gracefully** - rolled back attachments remain in the source folder.
707+
708+
> **CRITICAL**: To preserve custom properties attached with attachments on UI, ensure these properties are defined in the target entity. If custom properties are not present in the target entity definition, they will be lost after the move and will not be visible on the UI.
709+
571710
## Support for link type attachments
572711

573712
> **Note:** Row-press is the new recommended approach for handling actions on attachment rows. With row-press enabled, the Attachments column will no longer appear as a separate action button. Instead, clicking on a row will automatically perform the appropriate action — opening a link or downloading a file, based on the attachment type.

sdm/pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,9 @@
565565
<exclude>
566566
com/sap/cds/sdm/service/handler/AttachmentCopyEventContext.class
567567
</exclude>
568+
<exclude>
569+
com/sap/cds/sdm/service/handler/AttachmentMoveEventContext.class
570+
</exclude>
568571
</excludes>
569572
</configuration>
570573
<executions>

sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,20 @@ private SDMConstants() {
9999
public static final String ANNOTATION_IS_MEDIA_DATA = "_is_media_data";
100100
public static final String DRAFT_READONLY_CONTEXT = "DRAFT_READONLY_CONTEXT";
101101
public static final String FAILED_TO_COPY_ATTACHMENT = "Failed to copy attachment";
102+
103+
// Error messages for move operations
104+
public static final String SDM_MOVE_OPERATION_FAILED = "SDM move operation failed";
105+
public static final String VALIDATION_FAILED_PREFIX = "Validation failed: ";
106+
public static final String VALIDATION_FAILED_DEFAULT_MESSAGE =
107+
"Validation failed: Unable to process attachment properties or metadata";
108+
public static final String INVALID_SECONDARY_PROPERTIES_PREFIX =
109+
"Invalid secondary properties detected: ";
110+
public static final String INVALID_SECONDARY_PROPERTIES_SUFFIX =
111+
". Attachment rolled back to source.";
112+
public static final String FAILED_TO_MOVE_ATTACHMENT = "Failed to move attachment";
113+
public static final String FAILED_TO_MOVE_ATTACHMENT_MSG = "SDM.Move.failedToMoveAttachmentError";
114+
public static final String MOVE_OPERATION_PARTIAL_FAILURE =
115+
"Move operation completed with some failures";
102116
public static final String FAILED_TO_FETCH_UP_ID = "Failed to fetch up_id";
103117
public static final String FAILED_TO_FETCH_FACET =
104118
"Invalid facet format, unable to extract required information.";
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.sap.cds.sdm.model;
2+
3+
import com.sap.cds.reflect.CdsEntity;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
/** Helper class to hold attachment move context information. */
9+
public class AttachmentMoveContext {
10+
private final String objectId;
11+
private final MoveAttachmentsRequest request;
12+
private final List<String> validSecondaryProperties;
13+
private final Map<String, String> entityAnnotations;
14+
private final CdsEntity targetEntity;
15+
private final AttachmentProcessingResults processingResults;
16+
private final List<Map<String, String>> failedAttachments;
17+
private List<String> invalidProperties; // Mutable field to track validation failures
18+
19+
public AttachmentMoveContext(
20+
String objectId,
21+
MoveAttachmentsRequest request,
22+
List<String> validSecondaryProperties,
23+
Map<String, String> entityAnnotations,
24+
CdsEntity targetEntity,
25+
AttachmentProcessingResults processingResults,
26+
List<Map<String, String>> failedAttachments) {
27+
this.objectId = objectId;
28+
this.request = request;
29+
this.validSecondaryProperties = validSecondaryProperties;
30+
this.entityAnnotations = entityAnnotations;
31+
this.targetEntity = targetEntity;
32+
this.processingResults = processingResults;
33+
this.failedAttachments = failedAttachments;
34+
}
35+
36+
public String getObjectId() {
37+
return objectId;
38+
}
39+
40+
public MoveAttachmentsRequest getRequest() {
41+
return request;
42+
}
43+
44+
public List<String> getValidSecondaryProperties() {
45+
return validSecondaryProperties;
46+
}
47+
48+
public Map<String, String> getEntityAnnotations() {
49+
return entityAnnotations;
50+
}
51+
52+
public CdsEntity getTargetEntity() {
53+
return targetEntity;
54+
}
55+
56+
public AttachmentProcessingResults getProcessingResults() {
57+
return processingResults;
58+
}
59+
60+
public List<Map<String, String>> getFailedAttachments() {
61+
return Collections.unmodifiableList(failedAttachments);
62+
}
63+
64+
public List<String> getInvalidProperties() {
65+
return invalidProperties;
66+
}
67+
68+
public void setInvalidProperties(List<String> invalidProperties) {
69+
this.invalidProperties = invalidProperties;
70+
}
71+
72+
/**
73+
* Internal method for adding failed attachments during processing. For internal use only - do not
74+
* expose to external callers.
75+
*/
76+
public void addFailedAttachment(Map<String, String> failure) {
77+
failedAttachments.add(failure);
78+
}
79+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.sap.cds.sdm.model;
2+
3+
import java.util.List;
4+
5+
/** Helper class to hold attachment processing results. */
6+
public class AttachmentProcessingResults {
7+
private final List<String> successfulObjectIds;
8+
private final List<List<String>> movedAttachmentsMetadata;
9+
private final List<CmisDocument> populatedDocuments;
10+
11+
public AttachmentProcessingResults(
12+
List<String> successfulObjectIds,
13+
List<List<String>> movedAttachmentsMetadata,
14+
List<CmisDocument> populatedDocuments) {
15+
this.successfulObjectIds = successfulObjectIds;
16+
this.movedAttachmentsMetadata = movedAttachmentsMetadata;
17+
this.populatedDocuments = populatedDocuments;
18+
}
19+
20+
public List<String> getSuccessfulObjectIds() {
21+
return successfulObjectIds;
22+
}
23+
24+
public List<List<String>> getMovedAttachmentsMetadata() {
25+
return movedAttachmentsMetadata;
26+
}
27+
28+
public List<CmisDocument> getPopulatedDocuments() {
29+
return populatedDocuments;
30+
}
31+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
44
import java.io.InputStream;
5+
import java.util.Map;
56
import lombok.AllArgsConstructor;
67
import lombok.Builder;
78
import lombok.Data;
@@ -19,6 +20,7 @@ public class CmisDocument {
1920
private InputStream content;
2021
private String parentId;
2122
private String folderId;
23+
private String sourceFolderId;
2224
private String repositoryId;
2325
private String status;
2426
private String mimeType;
@@ -28,4 +30,5 @@ public class CmisDocument {
2830
private String contentId;
2931
private String type;
3032
private String description;
33+
private Map<String, Object> secondaryProperties;
3134
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.sap.cds.sdm.model;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
/** Result class for copyAttachmentsToSDM method. */
7+
public class CopyAttachmentsResult {
8+
private final List<Map<String, String>> attachmentsMetadata;
9+
private final List<CmisDocument> populatedDocuments;
10+
11+
public CopyAttachmentsResult(
12+
List<Map<String, String>> attachmentsMetadata, List<CmisDocument> populatedDocuments) {
13+
this.attachmentsMetadata = attachmentsMetadata;
14+
this.populatedDocuments = populatedDocuments;
15+
}
16+
17+
public List<Map<String, String>> getAttachmentsMetadata() {
18+
return attachmentsMetadata;
19+
}
20+
21+
public List<CmisDocument> getPopulatedDocuments() {
22+
return populatedDocuments;
23+
}
24+
}

0 commit comments

Comments
 (0)