Skip to content
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei
- Copy attachments: Provides the capability to copy attachments from one entity to another entity.
- Link as attachments: Provides the capability to support link or URL as attachments.
- Edit Link-type attachments: Provides the capability to update URL of link-type attachments.

- 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.
## Table of Contents

- [Pre-Requisites](#pre-requisites)
Expand All @@ -35,6 +35,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei
- [Support for Copy attachments](#support-for-copy-attachments)
- [Support for Link type attachments](#support-for-link-type-attachments)
- [Support for Edit of Link type attachments](#support-for-edit-of-link-type-attachments)
- [Support for Localization](#support-for-localization)
- [Known Restrictions](#known-restrictions)
- [Support, Feedback, Contributing](#support-feedback-contributing)
- [Code of Conduct](#code-of-conduct)
Expand Down Expand Up @@ -859,7 +860,26 @@ annotate Attachments with @Common: {SideEffects #ContentChanged: {
- Replace `AdminService` in `Action: 'AdminService.editLink'` with the name of your service.
- Repeat for other entities and elements if you have defined multiple `composition of many Attachments`.

## Support for Localization
If the UI fields have to be available in the local language of the leading application ensure to add the below fields in the i18n_[languagecode].properties file under app/_i18n folder.
Default language translations are present in i18n.properties files. If leading application does not provide any keys and values in their language properties files then default english language messages are shown to the user.

Example i18n_de.properties for german language.
```
Attachment=Attachment
Attachments=Attachments
Note= Attachment Note
Filename=File Name
linkUrl=Link Url
type=Type
```
For the exception messages as well the translation can be done by adding the translation to messages_[languagecode].properties files present under srv/src/main/resources.
Default language translations are present in messages.properties. If leading application does not provide any keys and values in their language properties files then default english language messages are shown to the user.

Example for german language
```
SDM.Attachments.maxCountError = Maximum number of attachments reached in German......
```
## Known Restrictions

- UI5 Version 1.135.0: This version causes error in upload of attachments.
Expand Down
51 changes: 48 additions & 3 deletions sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ private SDMConstants() {
"Upload not supported for versioned repositories.";
public static final String VIRUS_REPO_ERROR_MORE_THAN_400MB =
"You cannot upload files that are larger than 400 MB";
public static final String VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE = "SDM.VirusRepoErrorMessage";
public static final String VIRUS_ERROR = "%s contains potential malware and cannot be uploaded.";
public static final String VIRUS_ERROR_MESSAGE = "SDM.VirusErrorMessage";
public static final String SDM_DUPLICATE_ATTACHMENT = "SDM.DuplicateAttachment";
public static final String REPOSITORY_ERROR = "Failed to get repository info.";
public static final String NOT_FOUND_ERROR = "Failed to read document.";
public static final String NAME_CONSTRAINT_WARNING_MESSAGE =
"Enter a valid file name for %s. The following characters are not supported: /, \\";
public static final String SDM_MISSING_ROLES_EXCEPTION_MSG =
"You do not have the required permissions to update attachments. Kindly contact the admin";
public static final String SDM_ROLES_ERROR_MESSAGE =
Expand Down Expand Up @@ -67,6 +67,18 @@ private SDMConstants() {
public static final String ATTACHMENT_MAXCOUNT_ERROR_MSG = "SDM.Attachments.maxCountError";
public static final String MAX_COUNT_ERROR_MESSAGE =
"Cannot upload more than %s attachments as set up by the application";

// Localized error message keys
public static final String VERSIONED_REPO_ERROR_MSG = "SDM.Repository.versionedRepoError";
public static final String USER_NOT_AUTHORISED_ERROR_MSG =
"SDM.Authorization.userNotAuthorizedError";
public static final String USER_NOT_AUTHORISED_ERROR_LINK_MSG =
"SDM.Authorization.userNotAuthorizedLinkError";
public static final String FAILED_TO_EDIT_LINK_MSG = "SDM.Link.failedToEditLinkError";
public static final String REPOSITORY_ERROR_MSG = "SDM.Repository.repositoryError";
public static final String FILE_NOT_FOUND_ERROR_MSG = "SDM.File.fileNotFoundError";
public static final String MIMETYPE_INVALID_ERROR_MSG = "SDM.File.mimetypeInvalidError";
public static final String FAILED_TO_FETCH_FACET_MSG = "SDM.Facet.failedToFetchFacetError";
public static final String NO_SDM_BINDING = "No SDM binding found";
public static final String DI_TOKEN_EXCHANGE_ERROR = "Error fetching DI token with JWT bearer";
public static final String DI_TOKEN_EXCHANGE_PARAMS =
Expand Down Expand Up @@ -96,6 +108,39 @@ private SDMConstants() {
public static final String FETCH_ATTACHMENT_COMPOSITION_ERROR =
"Failed to fetch attachment composition";

// Error messages for ServiceException
public static final String FAILED_TO_EDIT_LINK = "Failed to edit link";
public static final String ERROR_IN_SETTING_TIMEOUT = "Error in setting timeout";
public static final String SDM_CREDENTIALS_MISSING_OR_INVALID =
"SDM credentials are missing or invalid.";
public static final String FAILED_TO_RETRIEVE_SDM_CREDENTIALS =
"Failed to retrieve SDM credentials.";
public static final String FAILED_TO_CREATE_HTTP_CLIENT = "Failed to create HTTP client.";
public static final String ERROR_WHILE_CREATING_HTTP_CLIENT = "Error while creating HTTP client.";
public static final String FAILED_TO_SET_REPOSITORY_DETAILS = "Failed to set repository details.";
public static final String FAILED_TO_SERIALIZE_REPOSITORY_OBJECT_TO_JSON =
"Failed to serialize repository object to JSON.";
public static final String FAILED_TO_CREATE_STRING_ENTITY = "Failed to create StringEntity.";
public static final String CLIENT_CREDENTIALS_MISSING_OR_INVALID =
"Client credentials are missing or invalid.";
public static final String FAILED_TO_CREATE_CLIENT_CREDENTIALS =
"Failed to create client credentials.";
public static final String FAILED_TO_REPLACE_SUBDOMAIN_IN_BASE_TOKEN_URL =
"Failed to replace subdomain in base token URL.";
public static final String ERROR_WHILE_FETCHING_REPOSITORY_ID =
"Error while fetching repository ID.";
public static final String UNEXPECTED_ERROR_WHILE_FETCHING_REPOSITORY_ID =
"Unexpected error while fetching repository ID.";
public static final String FAILED_TO_OFFBOARD_REPOSITORY = "Failed to offboard repository.";
public static final String ERROR_WHILE_OFFBOARDING_REPOSITORY =
"Error while offboarding repository.";
public static final String UNEXPECTED_ERROR_WHILE_OFFBOARDING_REPOSITORY =
"Unexpected error while offboarding repository.";
public static final String FAILED_TO_PARSE_REPOSITORY_RESPONSE =
"Failed to parse repository response";
public static final String ERROR_IN_SETTING_TIMEOUT_MESSAGE = "Error in setting timeout";
public static final String FAILED_TO_CREATE_FOLDER = "Failed to create folder";

public static String nameConstraintMessage(
List<String> fileNameWithRestrictedCharacters, String operation) {
// Create the base message
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.sap.cds.sdm.handler.applicationservice;

import com.sap.cds.CdsData;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.sdm.caching.CacheConfig;
import com.sap.cds.sdm.caching.SecondaryPropertiesKey;
Expand Down Expand Up @@ -383,22 +381,4 @@ private void handleWarnings(
context.getMessages().warn(SDMConstants.noSDMRolesMessage(noSDMRoles, "create"));
}
}

private List<String> getEntityCompositions(CdsCreateEventContext context) {
List<CdsElement> compositions = context.getTarget().compositions().toList();
List<String> attachmentsCompositionList = new ArrayList<>();
for (CdsElement cdsElement : compositions) {
if (cdsElement != null) {
CdsAssociationType cdsAssociationType = cdsElement.getType();
String targetAspect =
cdsAssociationType.getTargetAspect().isPresent()
? cdsAssociationType.getTargetAspect().get().getQualifiedName()
: null;
if (targetAspect != null && targetAspect.equalsIgnoreCase("sap.attachments.Attachments")) {
attachmentsCompositionList.add(cdsElement.getName());
}
}
}
return attachmentsCompositionList;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.sap.cds.sdm.handler.applicationservice;

import com.sap.cds.CdsData;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.sdm.caching.CacheConfig;
import com.sap.cds.sdm.caching.SecondaryPropertiesKey;
Expand Down Expand Up @@ -378,22 +376,4 @@ private void handleWarnings(
context.getMessages().warn(SDMConstants.noSDMRolesMessage(noSDMRoles, "update"));
}
}

private List<String> getEntityCompositions(CdsUpdateEventContext context) {
List<CdsElement> compositions = context.getTarget().compositions().toList();
List<String> attachmentsCompositionList = new ArrayList<>();
for (CdsElement cdsElement : compositions) {
if (cdsElement != null) {
CdsAssociationType cdsAssociationType = cdsElement.getType();
String targetAspect =
cdsAssociationType.getTargetAspect().isPresent()
? cdsAssociationType.getTargetAspect().get().getQualifiedName()
: null;
if (targetAspect != null && targetAspect.equalsIgnoreCase("sap.attachments.Attachments")) {
attachmentsCompositionList.add(cdsElement.getName());
}
}
}
return attachmentsCompositionList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private void executeHttpPost(
try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(uploadFile)) {
formResponse(cmisDocument, finalResponse, response);
} catch (IOException e) {
throw new ServiceException("Error in setting timeout", e);
throw new ServiceException(SDMConstants.ERROR_IN_SETTING_TIMEOUT, e);
}
}

Expand Down
40 changes: 20 additions & 20 deletions sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ public String onboardRepository(Repository repository)
sdmCredentials = tokenHandler.getSDMCredentials();
if (sdmCredentials == null || sdmCredentials.getUrl() == null) {
logger.error("SDM credentials are missing or invalid.");
throw new ServiceException("SDM credentials are missing or invalid.");
throw new ServiceException(SDMConstants.SDM_CREDENTIALS_MISSING_OR_INVALID);
}
} catch (Exception e) {
logger.error("Failed to retrieve SDM credentials: " + e.getMessage());
throw new ServiceException("Failed to retrieve SDM credentials.", e);
throw new ServiceException(SDMConstants.FAILED_TO_RETRIEVE_SDM_CREDENTIALS, e);
}

HttpClient httpClient = null;
Expand All @@ -61,11 +61,11 @@ public String onboardRepository(Repository repository)
null, null, repository.getSubdomain(), "TECHNICAL_CREDENTIALS_FLOW");
if (httpClient == null) {
logger.error("Failed to create HTTP client.");
throw new ServiceException("Failed to create HTTP client.");
throw new ServiceException(SDMConstants.FAILED_TO_CREATE_HTTP_CLIENT);
}
} catch (Exception e) {
logger.error("Error while creating HTTP client: " + e.getMessage());
throw new ServiceException("Error while creating HTTP client.", e);
throw new ServiceException(SDMConstants.ERROR_WHILE_CREATING_HTTP_CLIENT, e);
}

String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES;
Expand All @@ -78,23 +78,23 @@ public String onboardRepository(Repository repository)
onboardRepository.setRepository(repository);
} catch (Exception e) {
logger.error("Failed to set repository details: " + e.getMessage());
throw new ServiceException("Failed to set repository details.", e);
throw new ServiceException(SDMConstants.FAILED_TO_SET_REPOSITORY_DETAILS, e);
}

String json;
try {
json = objectMapper.writeValueAsString(onboardRepository);
} catch (JsonProcessingException e) {
logger.error("Failed to serialize repository object to JSON: " + e.getMessage());
throw new ServiceException("Failed to serialize repository object to JSON.", e);
throw new ServiceException(SDMConstants.FAILED_TO_SERIALIZE_REPOSITORY_OBJECT_TO_JSON, e);
}

StringEntity entity;
try {
entity = new StringEntity(json);
} catch (UnsupportedEncodingException e) {
logger.error("Failed to create StringEntity: " + e.getMessage());
throw new ServiceException("Failed to create StringEntity.", e);
throw new ServiceException(SDMConstants.FAILED_TO_CREATE_STRING_ENTITY, e);
}

onboardingReq.setEntity(entity);
Expand Down Expand Up @@ -146,11 +146,11 @@ public String offboardRepository(String subdomain) {
|| sdmCredentials.getUrl() == null
|| sdmCredentials.getBaseTokenUrl() == null) {
logger.error("SDM credentials are missing or invalid.");
throw new ServiceException("SDM credentials are missing or invalid.");
throw new ServiceException(SDMConstants.SDM_CREDENTIALS_MISSING_OR_INVALID);
}
} catch (Exception e) {
logger.error("Failed to retrieve SDM credentials: " + e.getMessage());
throw new ServiceException("Failed to retrieve SDM credentials.", e);
throw new ServiceException(SDMConstants.FAILED_TO_RETRIEVE_SDM_CREDENTIALS, e);
}

ClientCredentials clientCredentials;
Expand All @@ -159,11 +159,11 @@ public String offboardRepository(String subdomain) {
new ClientCredentials(sdmCredentials.getClientId(), sdmCredentials.getClientSecret());
if (clientCredentials.getId() == null || clientCredentials.getSecret() == null) {
logger.error("Client credentials are missing or invalid.");
throw new ServiceException("Client credentials are missing or invalid.");
throw new ServiceException(SDMConstants.CLIENT_CREDENTIALS_MISSING_OR_INVALID);
}
} catch (Exception e) {
logger.error("Failed to create client credentials: " + e.getMessage());
throw new ServiceException("Failed to create client credentials.", e);
throw new ServiceException(SDMConstants.FAILED_TO_CREATE_CLIENT_CREDENTIALS, e);
}

String baseTokenUrl = sdmCredentials.getBaseTokenUrl();
Expand All @@ -174,7 +174,7 @@ public String offboardRepository(String subdomain) {
baseTokenUrl = baseTokenUrl.replace(providersubdomain, subdomain);
} catch (Exception e) {
logger.error("Failed to replace subdomain in base token URL: " + e.getMessage());
throw new ServiceException("Failed to replace subdomain in base token URL.", e);
throw new ServiceException(SDMConstants.FAILED_TO_REPLACE_SUBDOMAIN_IN_BASE_TOKEN_URL, e);
}
}

Expand All @@ -196,11 +196,11 @@ public String offboardRepository(String subdomain) {
httpClient = factory.createHttpClient(destination);
if (httpClient == null) {
logger.error("Failed to create HTTP client.");
throw new ServiceException("Failed to create HTTP client.");
throw new ServiceException(SDMConstants.FAILED_TO_CREATE_HTTP_CLIENT);
}
} catch (Exception e) {
logger.error("Error while creating HTTP client: " + e.getMessage());
throw new ServiceException("Error while creating HTTP client.", e);
throw new ServiceException(SDMConstants.ERROR_WHILE_CREATING_HTTP_CLIENT, e);
}

String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/";
Expand All @@ -215,10 +215,10 @@ public String offboardRepository(String subdomain) {
}
} catch (IOException e) {
logger.error("Error while fetching repository ID: " + e.getMessage());
throw new ServiceException("Error while fetching repository ID.", e);
throw new ServiceException(SDMConstants.ERROR_WHILE_FETCHING_REPOSITORY_ID, e);
} catch (Exception e) {
logger.error("Unexpected error while fetching repository ID: " + e.getMessage());
throw new ServiceException("Unexpected error while fetching repository ID.", e);
throw new ServiceException(SDMConstants.UNEXPECTED_ERROR_WHILE_FETCHING_REPOSITORY_ID, e);
}

sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/" + repoId;
Expand All @@ -234,17 +234,17 @@ public String offboardRepository(String subdomain) {
return "Repository with ID " + SDMConstants.REPOSITORY_ID + " not found.";
}
logger.error("Failed to offboard repository : " + responseString);
throw new ServiceException("Failed to offboard repository.", responseString);
throw new ServiceException(SDMConstants.FAILED_TO_OFFBOARD_REPOSITORY, responseString);
}

logger.info("Repository " + repoId + " Offboarded");
return "Repository " + repoId + " Offboarded";
} catch (IOException e) {
logger.error("Error while offboarding repository: " + e.getMessage());
throw new ServiceException("Error while offboarding repository.", e);
throw new ServiceException(SDMConstants.ERROR_WHILE_OFFBOARDING_REPOSITORY, e);
} catch (Exception e) {
logger.error("Unexpected error while offboarding repository: " + e.getMessage());
throw new ServiceException("Unexpected error while offboarding repository.", e);
throw new ServiceException(SDMConstants.UNEXPECTED_ERROR_WHILE_OFFBOARDING_REPOSITORY, e);
}
}

Expand All @@ -268,7 +268,7 @@ private String getRepositoryId(String jsonString) {
}
}
} catch (Exception e) {
throw new ServiceException("Failed to parse repository response", e);
throw new ServiceException(SDMConstants.FAILED_TO_PARSE_REPOSITORY_RESPONSE, e);
}
return null;
}
Expand Down
15 changes: 11 additions & 4 deletions sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private void executeHttpPost(
try (var response = (CloseableHttpResponse) httpClient.execute(uploadFile)) {
formResponse(cmisDocument, finalResponse, response);
} catch (IOException e) {
throw new ServiceException("Error in setting timeout", e.getMessage());
throw new ServiceException(SDMConstants.ERROR_IN_SETTING_TIMEOUT_MESSAGE, e.getMessage());
}
}

Expand Down Expand Up @@ -356,7 +356,14 @@ public void readDocument(
if (responseCode != 200) {
response.close();
if (responseCode == 404) {
throw new ServiceException(SDMConstants.FILE_NOT_FOUND_ERROR);
String errorMessage =
context
.getCdsRuntime()
.getLocalizedMessage(
"SDM.File.fileNotFoundError", null, context.getParameterInfo().getLocale());
if (errorMessage.equalsIgnoreCase(SDMConstants.FILE_NOT_FOUND_ERROR_MSG))
throw new ServiceException(SDMConstants.FILE_NOT_FOUND_ERROR);
throw new ServiceException(errorMessage);
}
throw new ServiceException("Unexpected code");
}
Expand Down Expand Up @@ -469,10 +476,10 @@ public String createFolder(
else if (responseCode == 403) {
throw new ServiceException(SDMConstants.USER_NOT_AUTHORISED_ERROR);
} else {
throw new ServiceException("Failed to create folder. " + responseBody);
throw new ServiceException(SDMConstants.FAILED_TO_CREATE_FOLDER + ". " + responseBody);
}
} catch (IOException e) {
throw new ServiceException("Failed to create folder " + e.getMessage());
throw new ServiceException(SDMConstants.FAILED_TO_CREATE_FOLDER + " " + e.getMessage());
}
}

Expand Down
Loading
Loading