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.7.1-SNAPSHOT</revision>
<revision>1.0.0-RC1</revision>
<java.version>17</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 @@ -20,6 +20,10 @@ public class ValidatedAttachmentData {
private final List<List<String>> movedAttachmentsMetadata;
private final List<CmisDocument> populatedDocuments;
private final CmisDocument sourceCmisDocument;
private final String createdBy;
private final java.time.Instant creationDate;
private final String lastModifiedBy;
private final java.time.Instant lastModificationDate;

public ValidatedAttachmentData(
String objectId,
Expand All @@ -33,7 +37,11 @@ public ValidatedAttachmentData(
List<String> successfulObjectIds,
List<List<String>> movedAttachmentsMetadata,
List<CmisDocument> populatedDocuments,
CmisDocument sourceCmisDocument) {
CmisDocument sourceCmisDocument,
String createdBy,
java.time.Instant creationDate,
String lastModifiedBy,
java.time.Instant lastModificationDate) {
this.objectId = objectId;
this.fileName = fileName;
this.mimeType = mimeType;
Expand All @@ -46,6 +54,10 @@ public ValidatedAttachmentData(
this.movedAttachmentsMetadata = movedAttachmentsMetadata;
this.populatedDocuments = populatedDocuments;
this.sourceCmisDocument = sourceCmisDocument;
this.createdBy = createdBy;
this.creationDate = creationDate;
this.lastModifiedBy = lastModifiedBy;
this.lastModificationDate = lastModificationDate;
}

public String getObjectId() {
Expand Down Expand Up @@ -119,4 +131,20 @@ public void addPopulatedDocument(CmisDocument document) {
public CmisDocument getSourceCmisDocument() {
return sourceCmisDocument;
}

public String getCreatedBy() {
return createdBy;
}

public java.time.Instant getCreationDate() {
return creationDate;
}

public String getLastModifiedBy() {
return lastModifiedBy;
}

public java.time.Instant getLastModificationDate() {
return lastModificationDate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,19 @@ private void processSingleAttachmentMove(AttachmentMoveContext moveContext) {
String movedObjectId = succinctProperties.optString("cmis:objectId");
String objectTypeId = succinctProperties.optString("cmis:objectTypeId");

// Extract managed fields from SDM response to retain original timestamps and users
String createdBy = succinctProperties.optString("cmis:createdBy", null);
Instant creationDate = null;
if (succinctProperties.has("cmis:creationDate")) {
creationDate = Instant.ofEpochMilli(succinctProperties.getLong("cmis:creationDate"));
}
String lastModifiedBy = succinctProperties.optString("cmis:lastModifiedBy", null);
Instant lastModificationDate = null;
if (succinctProperties.has("cmis:lastModificationDate")) {
lastModificationDate =
Instant.ofEpochMilli(succinctProperties.getLong("cmis:lastModificationDate"));
}

// Determine attachment type based on cmis:objectTypeId from SDM response
// Link attachments: "sap:link" -> "sap-icon://internet-browser"
// Document attachments: "cmis:document" -> "sap-icon://document"
Expand Down Expand Up @@ -1153,7 +1166,11 @@ private void processSingleAttachmentMove(AttachmentMoveContext moveContext) {
moveContext.getProcessingResults().getSuccessfulObjectIds(),
moveContext.getProcessingResults().getMovedAttachmentsMetadata(),
moveContext.getProcessingResults().getPopulatedDocuments(),
cmisDocument);
cmisDocument,
createdBy,
creationDate,
lastModifiedBy,
lastModificationDate);
processValidatedAttachment(validatedData);
}

Expand Down Expand Up @@ -1221,7 +1238,13 @@ private void processValidatedAttachment(ValidatedAttachmentData data) {
data.getFileName(),
data.getMimeType(),
data.getDescription(),
data.getMovedObjectId()));
data.getMovedObjectId(),
data.getCreatedBy() != null ? data.getCreatedBy() : "",
data.getCreationDate() != null ? data.getCreationDate().toString() : "",
data.getLastModifiedBy() != null ? data.getLastModifiedBy() : "",
data.getLastModificationDate() != null
? data.getLastModificationDate().toString()
: ""));
data.addPopulatedDocument(populatedDocument);
}

Expand Down Expand Up @@ -1519,96 +1542,195 @@ private String resolveUpIdKey(EventContext context, String parentEntity, String
* @param data encapsulated draft entry creation data
*/
private void createDraftEntriesForMove(DraftEntryMoveData data) {

for (int i = 0; i < data.getMovedAttachmentsMetadata().size(); i++) {
List<String> attachmentMetadata = data.getMovedAttachmentsMetadata().get(i);
CmisDocument cmisDocument = data.getPopulatedDocuments().get(i);
Map<String, Object> updatedFields = new HashMap<>();

String fileName = attachmentMetadata.get(0);
String mimeType = attachmentMetadata.get(1);
String description = attachmentMetadata.get(2);
String newObjectId = attachmentMetadata.get(3);
Map<String, Object> updatedFields =
buildUpdatedFieldsForMove(attachmentMetadata, cmisDocument, data);

updatedFields.put(OBJECT_ID_KEY, newObjectId);
updatedFields.put("repositoryId", data.getRepositoryId());
updatedFields.put("folderId", data.getFolderId());
updatedFields.put("status", "Clean");
updatedFields.put("mimeType", mimeType);
updatedFields.put("type", cmisDocument.getType());
updatedFields.put("fileName", fileName);
updatedFields.put("note", description);
updatedFields.put("HasDraftEntity", false);
updatedFields.put("HasActiveEntity", false);
updatedFields.put("linkUrl", cmisDocument.getUrl());
updatedFields.put(
"contentId",
newObjectId
+ ":"
+ data.getFolderId()
+ ":"
+ data.getParentEntity()
+ "."
+ data.getCompositionName()
+ ":"
+ mimeType);
updatedFields.put(data.getUpIdKey(), data.getUpID());
performDraftInsertWithRetry(updatedFields, data);
}
}

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

logger.info(
"Final DB insert map for attachment {} contains {} fields: {}",
newObjectId,
updatedFields.size(),
updatedFields.keySet());
String fileName = attachmentMetadata.get(0);
String mimeType = attachmentMetadata.get(1);
String description = attachmentMetadata.get(2);
String newObjectId = attachmentMetadata.get(3);

String baseKeyField =
data.getUpIdKey() != null ? data.getUpIdKey().replace("up__", "") : "ID";
var insert =
Insert.into(
data.getParentEntity(),
e ->
e.filter(e.get(baseKeyField).eq(data.getUpID()))
.to(data.getCompositionName()))
.entry(updatedFields);
Map<String, Object> updatedFields =
buildBasicFields(newObjectId, fileName, mimeType, description, cmisDocument, data);

DraftService matchingService =
draftService.stream()
.filter(ds -> data.getParentEntity().contains(ds.getName()))
.findFirst()
.orElse(null);
addManagedFieldsForMove(updatedFields, attachmentMetadata);
addSecondaryPropertiesForMove(updatedFields, cmisDocument, newObjectId);

if (matchingService != null) {
// Wrap DB insert with retry logic to handle transient DB failures
try {
Flowable.fromCallable(
() -> {
matchingService.newDraft(insert);
return true;
})
.retryWhen(com.sap.cds.sdm.service.RetryUtils.retryLogic(5)) // Retry up to 5 times
.blockingFirst();
} catch (Exception e) {
throw new ServiceException(
"Failed to insert attachment entry in DB after retries: " + e.getMessage(), e);
}
} else {
throw new ServiceException(
"No suitable service found for entity: " + data.getParentEntity());
}
logger.info(
"Final DB insert map for attachment {} contains {} fields: {}",
newObjectId,
updatedFields.size(),
updatedFields.keySet());

return updatedFields;
}

/**
* Builds the basic field map with core attachment properties.
*
* @param newObjectId the new object ID
* @param fileName the file name
* @param mimeType the MIME type
* @param description the description
* @param cmisDocument the CMIS document
* @param data the draft entry move data
* @return map with basic fields
*/
private Map<String, Object> buildBasicFields(
String newObjectId,
String fileName,
String mimeType,
String description,
CmisDocument cmisDocument,
DraftEntryMoveData data) {

Map<String, Object> fields = new HashMap<>();
fields.put(OBJECT_ID_KEY, newObjectId);
fields.put("repositoryId", data.getRepositoryId());
fields.put("folderId", data.getFolderId());
fields.put("status", "Clean");
fields.put("mimeType", mimeType);
fields.put("type", cmisDocument.getType());
fields.put("fileName", fileName);
fields.put("note", description);
fields.put("HasDraftEntity", false);
fields.put("HasActiveEntity", false);
fields.put("IsActiveEntity", true);
fields.put("linkUrl", cmisDocument.getUrl());
fields.put(
"contentId",
newObjectId
+ ":"
+ data.getFolderId()
+ ":"
+ data.getParentEntity()
+ "."
+ data.getCompositionName()
+ ":"
+ mimeType);
fields.put(data.getUpIdKey(), data.getUpID());

return fields;
}

/**
* Adds managed fields (createdBy, createdAt, modifiedBy, modifiedAt) to the fields map.
*
* @param fields the fields map to update
* @param attachmentMetadata the metadata list containing managed field values
*/
private void addManagedFieldsForMove(
Map<String, Object> fields, List<String> attachmentMetadata) {

String createdBy = attachmentMetadata.size() > 4 ? attachmentMetadata.get(4) : null;
String creationDate = attachmentMetadata.size() > 5 ? attachmentMetadata.get(5) : null;
String lastModifiedBy = attachmentMetadata.size() > 6 ? attachmentMetadata.get(6) : null;
String lastModificationDate = attachmentMetadata.size() > 7 ? attachmentMetadata.get(7) : null;

addFieldIfPresent(fields, "createdBy", createdBy);
addInstantFieldIfPresent(fields, "createdAt", creationDate);
addFieldIfPresent(fields, "modifiedBy", lastModifiedBy);
addInstantFieldIfPresent(fields, "modifiedAt", lastModificationDate);
}

/**
* Adds a field to the map if the value is present and not empty.
*
* @param fields the fields map to update
* @param fieldName the field name
* @param value the field value
*/
private void addFieldIfPresent(Map<String, Object> fields, String fieldName, String value) {
if (value != null && !value.isEmpty()) {
fields.put(fieldName, value);
}
}

/**
* Adds an Instant field to the map if the value is present and not empty.
*
* @param fields the fields map to update
* @param fieldName the field name
* @param value the string representation of the Instant
*/
private void addInstantFieldIfPresent(
Map<String, Object> fields, String fieldName, String value) {
if (value != null && !value.isEmpty()) {
fields.put(fieldName, java.time.Instant.parse(value));
}
}

/**
* Adds secondary properties from the CMIS document to the fields map.
*
* @param fields the fields map to update
* @param cmisDocument the CMIS document with secondary properties
* @param objectId the object ID for logging
*/
private void addSecondaryPropertiesForMove(
Map<String, Object> fields, CmisDocument cmisDocument, String objectId) {

if (cmisDocument.getSecondaryProperties() != null) {
logger.info(
"Adding {} secondary properties to DB insert for attachment {}: {}",
cmisDocument.getSecondaryProperties().size(),
objectId,
cmisDocument.getSecondaryProperties());
fields.putAll(cmisDocument.getSecondaryProperties());
} else {
logger.warn("No secondary properties to add for attachment {}", objectId);
}
}

/**
* Performs database insert with retry logic for the given fields.
*
* @param updatedFields the fields to insert
* @param data the draft entry move data containing entity information
* @throws ServiceException if insert fails after retries
*/
private void performDraftInsertWithRetry(
Map<String, Object> updatedFields, DraftEntryMoveData data) {

String baseKeyField = data.getUpIdKey() != null ? data.getUpIdKey().replace("up__", "") : "ID";
var insert =
Insert.into(
data.getParentEntity(),
e -> e.filter(e.get(baseKeyField).eq(data.getUpID())).to(data.getCompositionName()))
.entry(updatedFields);

// Insert directly into active entity (not draft) using persistenceService
// Wrap DB insert with retry logic to handle transient DB failures
try {
Flowable.fromCallable(
() -> {
persistenceService.run(insert);
return true;
})
.retryWhen(com.sap.cds.sdm.service.RetryUtils.retryLogic(5)) // Retry up to 5 times
.blockingFirst();
} catch (Exception e) {
throw new ServiceException(
"Failed to insert attachment entry in DB after retries: " + e.getMessage(), e);
}
}

Expand Down
Loading
Loading