Skip to content

Commit 50db637

Browse files
Merge pull request #124 from cap-java/CustomPropFinalBranch
[SDMEXT-1040]: Support Secondary Properties In Attachments
2 parents d807cc1 + 90c376e commit 50db637

File tree

17 files changed

+2118
-410
lines changed

17 files changed

+2118
-410
lines changed

.github/workflows/sonarqube.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
-Dsonar.junit.reportPaths=sdm/target/surefire-reports \
5555
-Dsonar.coverage.jacoco.xmlReportPaths=sdm/target/site/jacoco/jacoco.xml \
5656
-Dsonar.inclusions=**/*.java \
57-
-Dsonar.exclusions=**/target/**,**/node_modules/**,sdm/src/main/test/**,cap-notebook/*.capnb,sdm/src/main/java/com/sap/cds/sdm/model/**,sdm/src/main/java/com/sap/cds/sdm/caching/CacheKey.java,sdm/src/main/java/com/sap/cds/sdm/caching/RepoKey.java,sdm/src/main/java/com/sap/cds/sdm/caching/TokenCacheKey.java \
57+
-Dsonar.exclusions=**/target/**,**/node_modules/**,sdm/src/main/test/**,cap-notebook/*.capnb,sdm/src/main/java/com/sap/cds/sdm/model/**,sdm/src/main/java/com/sap/cds/sdm/caching/CacheKey.java,sdm/src/main/java/com/sap/cds/sdm/caching/RepoKey.java,sdm/src/main/java/com/sap/cds/sdm/caching/TokenCacheKey.java,sdm/src/main/java/com/sap/cds/sdm/caching/SecondaryTypesKey.java \
5858
-Dsonar.java.file.suffixes=.java \
5959
-Dsonar.host.url=${{ secrets.SONAR_HOST_URL }} \
6060
-Dsonar.login=${{ secrets.SONAR_TOKEN }} \

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei
2121
- [Deploying and testing the application](#deploying-and-testing-the-application)
2222
- [Use com.sap.cds:sdm dependency](#use-comsapcdssdm-dependency)
2323
- [Support for Multitenancy](#support-for-multitenancy)
24+
- [Support for Custom Properties](#support-for-custom-properties)
2425
- [Known Restrictions](#known-restrictions)
2526
- [Support, Feedback, Contributing](#support-feedback-contributing)
2627
- [Code of Conduct](#code-of-conduct)
@@ -317,10 +318,64 @@ String response = sdmAdminService.onboardRepository(repository);
317318
When the application is deployed as a SaaS application using the code above, tenants automatically onboard a repository upon subscription.
318319
- When the application is deployed as a SaaS application with above code, tenants on subscribing the SaaS application gets onboarded automatically.
319320

321+
## Support for Custom Properties
322+
323+
Custom properties are supported via the usage of CMIS secondary type properties. Follow the below steps to add and use custom properties.
324+
325+
1. If the repository does not contain secondary types and properties, create CMIS secondary types and properties using the [Create Secondary Type API](https://api.sap.com/api/CreateSecondaryTypeApi/overview). The property definition must contain the following section for the CAP plugin to process the property.
326+
327+
```json
328+
"mcm:miscellaneous": {
329+
"isPartOfTable": "true"
330+
}
331+
```
332+
333+
With this, the secondary type and properties definition will be as per the sample given below
334+
335+
```json
336+
{
337+
"id": "Working:DocumentInfo",
338+
"displayName": "Document Info",
339+
"baseId": "cmis:secondary",
340+
"parentId": "cmis:secondary",
341+
...
342+
},
343+
"propertyDefinitions": {
344+
"Working:DocumentInfoRecord": {
345+
"id": "Working:DocumentInfoRecord",
346+
"displayName": "Document Info Record",
347+
...
348+
"mcm:miscellaneous": { <-- Required section in the property definition
349+
"isPartOfTable": "true"
350+
}
351+
}
352+
}
353+
}
354+
```
355+
356+
2. Using secondary properties in CAP Application.
357+
- Extend the `Attachments` aspect with the secondary properties in the previously created _attachment-extension.cds_ file.
358+
- Annotate the secondary properties with `@SDM.Attachments.AdditionalProperty`.
359+
- If the property id contains a `:`, replace it with a triple underscore `___`.
360+
361+
Refer the following example from a sample Bookshop app:
362+
363+
```cds
364+
extend Attachments with {
365+
Working___DocumentInfoRecord : String @SDM.Attachments.AdditionalProperty @(title: '{i18n>property1}');
366+
}
367+
```
368+
369+
> **Note**
370+
>
371+
> SDM supports secondary properties with data types `String`, `Boolean`, `Decimal`, `Integer` and `DateTime`.
372+
373+
320374
## Known Restrictions
321375

322376
- Repository : This plugin does not support the use of versioned repositories.
323377
- File size : Attachments are limited to a maximum size of 700 MB. If the repository is [onboarded](https://help.sap.com/docs/document-management-service/sap-document-management-service/internal-repository?version=Cloud&locale=en-US) with virus scan enabled for all files, attachments are limited to a maximum size of 400 MB.
378+
- Datatypes for custom properties : Custom properties are supported for the following data types `String`, `Boolean`, `Decimal`, `Integer` and `DateTime`.
324379

325380
## Support, Feedback, Contributing
326381

sdm/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@
547547
<limit implementation="org.jacoco.report.check.Limit">
548548
<counter>BRANCH</counter>
549549
<value>COVEREDRATIO</value>
550-
<minimum>0.90</minimum>
550+
<minimum>0.80</minimum>
551551
</limit>
552552
<limit implementation="org.jacoco.report.check.Limit">
553553
<counter>CLASS</counter>

sdm/src/main/java/com/sap/cds/sdm/caching/CacheConfig.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sap.cds.sdm.caching;
22

3+
import java.util.List;
34
import java.util.concurrent.TimeUnit;
45
import org.ehcache.Cache;
56
import org.ehcache.CacheManager;
@@ -16,6 +17,7 @@ public class CacheConfig {
1617
private static Cache<CacheKey, String> clientCredentialsTokenCache;
1718
private static Cache<TokenCacheKey, String> userAuthoritiesTokenCache;
1819
private static Cache<RepoKey, String> versionedRepoCache;
20+
private static Cache<SecondaryTypesKey, List<String>> secondaryTypesCache;
1921
private static final int HEAP_SIZE = 1000;
2022
private static final int USER_TOKEN_EXPIRY = 660;
2123
private static final int ACCESS_TOKEN_EXPIRY = 660;
@@ -63,6 +65,15 @@ public static void initializeCache() {
6365
.withExpiry(
6466
Expirations.timeToLiveExpiration(
6567
new Duration(USER_TOKEN_EXPIRY, TimeUnit.MINUTES))));
68+
69+
secondaryTypesCache =
70+
cacheManager.createCache(
71+
"secondaryTypes",
72+
CacheConfigurationBuilder.newCacheConfigurationBuilder(
73+
SecondaryTypesKey.class,
74+
(Class<List<String>>) (Class<?>) List.class,
75+
ResourcePoolsBuilder.heap(HEAP_SIZE))
76+
.withExpiry(Expirations.noExpiration()));
6677
}
6778

6879
public static Cache<CacheKey, String> getUserTokenCache() {
@@ -80,4 +91,8 @@ public static Cache<CacheKey, String> getClientCredentialsTokenCache() {
8091
public static Cache<RepoKey, String> getVersionedRepoCache() {
8192
return versionedRepoCache;
8293
}
94+
95+
public static Cache<SecondaryTypesKey, List<String>> getSecondaryTypesCache() {
96+
return secondaryTypesCache;
97+
}
8398
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.sap.cds.sdm.caching;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
@Data
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
public class SecondaryTypesKey {
11+
private String repositoryId;
12+
}

sdm/src/main/java/com/sap/cds/sdm/configuration/Registration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) {
6060

6161
SDMService sdmService = new SDMServiceImpl(binding, connectionPool);
6262
configurer.eventHandler(buildReadHandler());
63-
configurer.eventHandler(new SDMCreateAttachmentsHandler(sdmService));
63+
configurer.eventHandler(new SDMCreateAttachmentsHandler(persistenceService, sdmService));
6464
configurer.eventHandler(new SDMUpdateAttachmentsHandler(persistenceService, sdmService));
6565
configurer.eventHandler(new SDMAttachmentsServiceHandler(persistenceService, sdmService));
6666
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ private SDMConstants() {
88
}
99

1010
public static final String REPOSITORY_ID = System.getenv("REPOSITORY_ID");
11+
public static final String SDM_ANNOTATION_ADDITIONALPROPERTY =
12+
"@SDM.Attachments.AdditionalProperty";
1113
public static final String DUPLICATE_FILE_IN_DRAFT_ERROR_MESSAGE =
1214
"The file(s) %s have been added multiple times. Please rename and try again.";
1315
public static final String FILES_RENAME_WARNING_MESSAGE =
@@ -41,6 +43,7 @@ private SDMConstants() {
4143
"Repository with name %s and id %s onboarded successfully";
4244
public static final String ONBOARD_REPO__ERROR_MESSAGE =
4345
"Error in onboarding repository with name %s";
46+
public static final String UPDATE_ATTACHMENT_ERROR = "Could not update the attachment";
4447

4548
public static String nameConstraintMessage(
4649
List<String> fileNameWithRestrictedCharacters, String operation) {
@@ -62,6 +65,22 @@ public static String nameConstraintMessage(
6265
return bulletPoints.toString();
6366
}
6467

68+
public static String secondaryPropertiesError(List<String> invalidSecondaryProperties) {
69+
// Create the base message
70+
String prefixMessage = "The following secondary properties are not supported.\n\n";
71+
72+
// Initialize the StringBuilder with the formatted message prefix
73+
StringBuilder bulletPoints = new StringBuilder(prefixMessage);
74+
75+
// Append each unsupported file name to the StringBuilder
76+
for (String file : invalidSecondaryProperties) {
77+
bulletPoints.append(String.format("\t• %s%n", file));
78+
}
79+
bulletPoints.append(
80+
"\nPlease contact your administrator for assistance with any necessary adjustments.");
81+
return bulletPoints.toString();
82+
}
83+
6584
public static String getDuplicateFilesError(String filename) {
6685
return String.format(DUPLICATE_FILES_ERROR, filename);
6786
}

sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMCreateAttachmentsHandler.java

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.sap.cds.sdm.handler.applicationservice;
22

33
import com.sap.cds.CdsData;
4+
import com.sap.cds.reflect.CdsEntity;
45
import com.sap.cds.sdm.constants.SDMConstants;
56
import com.sap.cds.sdm.handler.TokenHandler;
67
import com.sap.cds.sdm.model.CmisDocument;
78
import com.sap.cds.sdm.model.SDMCredentials;
9+
import com.sap.cds.sdm.persistence.DBQuery;
810
import com.sap.cds.sdm.service.SDMService;
911
import com.sap.cds.sdm.utilities.SDMUtils;
1012
import com.sap.cds.services.ServiceException;
@@ -16,18 +18,23 @@
1618
import com.sap.cds.services.handler.annotations.Before;
1719
import com.sap.cds.services.handler.annotations.HandlerOrder;
1820
import com.sap.cds.services.handler.annotations.ServiceName;
21+
import com.sap.cds.services.persistence.PersistenceService;
1922
import java.io.IOException;
2023
import java.util.ArrayList;
24+
import java.util.HashMap;
2125
import java.util.List;
2226
import java.util.Map;
27+
import java.util.Optional;
2328
import java.util.Set;
2429

2530
@ServiceName(value = "*", type = ApplicationService.class)
2631
public class SDMCreateAttachmentsHandler implements EventHandler {
2732

33+
private final PersistenceService persistenceService;
2834
private final SDMService sdmService;
2935

30-
public SDMCreateAttachmentsHandler(SDMService sdmService) {
36+
public SDMCreateAttachmentsHandler(PersistenceService persistenceService, SDMService sdmService) {
37+
this.persistenceService = persistenceService;
3138
this.sdmService = sdmService;
3239
}
3340

@@ -82,6 +89,11 @@ private void processAttachment(
8289
List<String> fileNameWithRestrictedCharacters,
8390
List<String> duplicateFileNameList)
8491
throws IOException {
92+
String id = (String) attachment.get("ID"); // Ensure appropriate cast to String
93+
Optional<CdsEntity> attachmentEntity =
94+
context.getModel().findEntity(context.getTarget().getQualifiedName() + ".attachments");
95+
String fileNameInDB;
96+
fileNameInDB = DBQuery.getAttachmentForID(attachmentEntity.get(), persistenceService, id);
8597
String filenameInRequest = (String) attachment.get("fileName");
8698
String objectId = (String) attachment.get("objectId");
8799
AuthenticationInfo authInfo = context.getAuthenticationInfo();
@@ -90,33 +102,61 @@ private void processAttachment(
90102
SDMCredentials sdmCredentials = TokenHandler.getSDMCredentials();
91103
String fileNameInSDM = sdmService.getObject(jwtToken, objectId, sdmCredentials);
92104

93-
if (fileNameInSDM != null && !fileNameInSDM.equals(filenameInRequest)) {
94-
if (Boolean.TRUE.equals(SDMUtils.isRestrictedCharactersInName(filenameInRequest))) {
95-
fileNameWithRestrictedCharacters.add(filenameInRequest);
96-
attachment.replace("fileName", fileNameInSDM);
105+
List<String> secondaryTypeProperties =
106+
SDMUtils.getSecondaryTypeProperties(attachmentEntity, attachment);
107+
Map<String, Object> propertiesMap = new HashMap<>();
108+
// For each property get the value
109+
if (!secondaryTypeProperties.isEmpty()) {
110+
for (String property : secondaryTypeProperties) {
111+
Object value = attachment.get(property);
112+
propertiesMap.put(property, value);
113+
}
114+
}
115+
// Get the updated secondary properties
116+
Map<String, String> updatedSecondaryProperties =
117+
SDMUtils.getUpdatedSecondaryProperties(
118+
attachmentEntity, attachment, persistenceService, secondaryTypeProperties);
119+
120+
if (Boolean.TRUE.equals(SDMUtils.isRestrictedCharactersInName(filenameInRequest))) {
121+
fileNameWithRestrictedCharacters.add(filenameInRequest);
122+
attachment.replace("fileName", fileNameInSDM);
123+
} else {
124+
CmisDocument cmisDocument = new CmisDocument();
125+
cmisDocument.setFileName(filenameInRequest);
126+
cmisDocument.setObjectId(objectId);
127+
if (fileNameInDB == null) {
128+
if (filenameInRequest != null) {
129+
updatedSecondaryProperties.put("filename", filenameInRequest);
130+
} else {
131+
throw new ServiceException("Filename cannot be empty");
132+
}
97133
} else {
98-
CmisDocument cmisDocument = new CmisDocument();
99-
cmisDocument.setFileName(filenameInRequest);
100-
cmisDocument.setObjectId(objectId);
101-
int responseCode = sdmService.renameAttachments(jwtToken, sdmCredentials, cmisDocument);
102-
switch (responseCode) {
103-
case 403:
104-
// SDM Roles for user are missing
105-
throw new ServiceException(SDMConstants.SDM_MISSING_ROLES_EXCEPTION_MSG, null);
134+
if (filenameInRequest == null) {
135+
throw new ServiceException("Filename cannot be empty");
136+
} else if (!fileNameInDB.equals(filenameInRequest)) {
137+
updatedSecondaryProperties.put("filename", filenameInRequest);
138+
}
139+
}
140+
int responseCode =
141+
sdmService.updateAttachments(
142+
jwtToken, sdmCredentials, cmisDocument, updatedSecondaryProperties);
143+
switch (responseCode) {
144+
case 403:
145+
// SDM Roles for user are missing
146+
throw new ServiceException(SDMConstants.SDM_MISSING_ROLES_EXCEPTION_MSG, null);
106147

107-
case 409:
108-
duplicateFileNameList.add(filenameInRequest);
109-
attachment.replace("fileName", fileNameInSDM);
110-
break;
148+
case 409:
149+
duplicateFileNameList.add(filenameInRequest);
150+
attachment.replace("fileName", fileNameInSDM);
151+
break;
111152

112-
case 200:
113-
case 201:
114-
// Success cases, do nothing
115-
break;
153+
case 200:
154+
case 201:
155+
// Success cases, do nothing
156+
break;
116157

117-
default:
118-
throw new ServiceException(SDMConstants.SDM_ROLES_ERROR_MESSAGE, null);
119-
}
158+
default:
159+
throw new ServiceException(SDMConstants.SDM_ROLES_ERROR_MESSAGE, null);
120160
}
121161
}
122162
}

0 commit comments

Comments
 (0)