diff --git a/README.md b/README.md index 7cb8be171..c1b827568 100644 --- a/README.md +++ b/README.md @@ -364,13 +364,13 @@ public void onSubscribe(SubscribeEventContext context) { repository.setDescription("Onboarding Repo Demo"); repository.setDisplayName(" Test Onboarding repo"); repository.setSubdomain(subdomain); + repository.setHashAlgorithms("SHA-256"); // Using SDMAdminServiceImpl onboardRepository() to onboard repository SDMAdminService sdmAdminService = new SDMAdminServiceImpl(); String response = sdmAdminService.onboardRepository(repository); } ``` - ```java @After(event = DeploymentService.EVENT_UNSUBSCRIBE) public void afterUnsubscribe(UnsubscribeEventContext context) { @@ -385,6 +385,11 @@ public void onSubscribe(SubscribeEventContext context) { String res = sdmAdminService.offboardRepository(subdomain); } ``` + +> **Note** +> +> Unsubscription will fail if an error occurs while deleting the repository, except when the error indicates that the repository was not found — in that case, the unsubscription will succeed. + When the application is deployed as a SaaS application with above code, a repository is onboarded automatically when a tenant subscribes the SaaS application. The same repository is deleted when the tenant unsubscribes from the SaaS application. The necessary params for the Repository onboarding can be found in the [documentation](https://help.sap.com/docs/document-management-service/sap-document-management-service/internal-repository). diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java b/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java index 7355883e6..fdd7783af 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java @@ -302,22 +302,29 @@ private void formResponse( String error = ""; try { String responseString = EntityUtils.toString(response.getEntity()); - JSONObject jsonResponse = new JSONObject(responseString); int responseCode = response.getStatusLine().getStatusCode(); if (responseCode == 201 || responseCode == 200) { + JSONObject jsonResponse = new JSONObject(responseString); JSONObject succinctProperties = jsonResponse.getJSONObject("succinctProperties"); status = "success"; objectId = succinctProperties.getString("cmis:objectId"); } else { - String message = jsonResponse.getString("message"); - if (responseCode == 409 - && "Malware Service Exception: Virus found in the file!".equals(message)) { - status = "virus"; - } else if (responseCode == 409) { - status = "duplicate"; - } else if (responseCode == 403) { + if (responseCode == 409) { + JSONObject jsonResponse = new JSONObject(responseString); + String message = jsonResponse.getString("message"); + JSONObject succinctProperties = jsonResponse.getJSONObject("succinctProperties"); + objectId = succinctProperties.getString("cmis:objectId"); + if ("Malware Service Exception: Virus found in the file!".equals(message)) { + status = "virus"; + } else { + status = "duplicate"; + } + } else if ((responseCode == 403) + && (responseString.equals("User does not have required scope"))) { status = "unauthorized"; } else { + JSONObject jsonResponse = new JSONObject(responseString); + String message = jsonResponse.getString("message"); status = "fail"; error = message; } diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java b/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java index 3bcb7e39d..3387149bb 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java @@ -17,6 +17,8 @@ import com.sap.cloud.security.config.ClientCredentials; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; @@ -35,50 +37,147 @@ public class SDMAdminServiceImpl implements SDMAdminService { @java.lang.Override public String onboardRepository(Repository repository) throws JsonProcessingException, UnsupportedEncodingException { - SDMCredentials sdmCredentials = tokenHandler.getSDMCredentials(); - var httpClient = - tokenHandler.getHttpClient( - null, null, repository.getSubdomain(), "TECHNICAL_CREDENTIALS_FLOW"); + if (repository == null) { + logger.error("Repository object is null. Cannot proceed with onboarding."); + throw new IllegalArgumentException("Repository object cannot be null."); + } + + SDMCredentials sdmCredentials; + try { + 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."); + } + } catch (Exception e) { + logger.error("Failed to retrieve SDM credentials: " + e.getMessage()); + throw new ServiceException("Failed to retrieve SDM credentials.", e); + } + + HttpClient httpClient = null; + try { + httpClient = + tokenHandler.getHttpClient( + 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."); + } + } catch (Exception e) { + logger.error("Error while creating HTTP client: " + e.getMessage()); + throw new ServiceException("Error while creating HTTP client.", e); + } + String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES; HttpPost onboardingReq = new HttpPost(sdmUrl); ObjectMapper objectMapper = new ObjectMapper(); RepositoryBody onboardRepository = new RepositoryBody(); - repository.setExternalId(REPOSITORY_ID); - onboardRepository.setRepository(repository); - String json = objectMapper.writeValueAsString(onboardRepository); - StringEntity entity = new StringEntity(json); + + try { + repository.setExternalId(REPOSITORY_ID); + onboardRepository.setRepository(repository); + } catch (Exception e) { + logger.error("Failed to set repository details: " + e.getMessage()); + throw new ServiceException("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); + } + + 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); + } + onboardingReq.setEntity(entity); - // Set the content type of the request onboardingReq.setHeader("Content-Type", "application/json"); + try (var response = (CloseableHttpResponse) httpClient.execute(onboardingReq)) { String responseString = EntityUtils.toString(response.getEntity()); + if ((responseString.contains(REPOSITORY_ID + " already exists")) && response.getStatusLine().getStatusCode() == 409) { return String.format( SDMConstants.REPOSITORY_ALREADY_EXIST, repository.getDisplayName(), REPOSITORY_ID); } - JsonObject jsonObject = JsonParser.parseString(responseString).getAsJsonObject(); - String repositoryId = jsonObject.get("id").getAsString(); + + JsonObject jsonObject; + jsonObject = JsonParser.parseString(responseString).getAsJsonObject(); + + String repositoryId; + if (jsonObject.has("id") && !jsonObject.get("id").isJsonNull()) { + repositoryId = jsonObject.get("id").getAsString(); + } else { + logger.error( + String.format(SDMConstants.ONBOARD_REPO_ERROR_MESSAGE, repository.getDisplayName()) + + " : " + + responseString); + throw new ServiceException( + String.format(SDMConstants.ONBOARD_REPO_ERROR_MESSAGE, repository.getDisplayName()), + responseString); + } + return String.format( SDMConstants.ONBOARD_REPO_MESSAGE, repository.getDisplayName(), repositoryId); - } catch (IOException e) { + } catch (Exception e) { + logger.error( + String.format(SDMConstants.ONBOARD_REPO_ERROR_MESSAGE, repository.getDisplayName()) + + " : " + + e.getMessage()); throw new ServiceException( - String.format(SDMConstants.ONBOARD_REPO_ERROR_MESSAGE, repository.getDisplayName()), - e.getMessage()); + String.format(SDMConstants.ONBOARD_REPO_ERROR_MESSAGE, repository.getDisplayName()), e); } } @java.lang.Override public String offboardRepository(String subdomain) { - SDMCredentials sdmCredentials = tokenHandler.getSDMCredentials(); - ClientCredentials clientCredentials = - new ClientCredentials(sdmCredentials.getClientId(), sdmCredentials.getClientSecret()); + SDMCredentials sdmCredentials; + try { + sdmCredentials = tokenHandler.getSDMCredentials(); + if (sdmCredentials == null + || sdmCredentials.getUrl() == null + || sdmCredentials.getBaseTokenUrl() == null) { + logger.error("SDM credentials are missing or invalid."); + throw new ServiceException("SDM credentials are missing or invalid."); + } + } catch (Exception e) { + logger.error("Failed to retrieve SDM credentials: " + e.getMessage()); + throw new ServiceException("Failed to retrieve SDM credentials.", e); + } + + ClientCredentials clientCredentials; + try { + clientCredentials = + 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."); + } + } catch (Exception e) { + logger.error("Failed to create client credentials: " + e.getMessage()); + throw new ServiceException("Failed to create client credentials.", e); + } + String baseTokenUrl = sdmCredentials.getBaseTokenUrl(); - if (subdomain != null && !subdomain.equals("")) { - String providersubdomain = - baseTokenUrl.substring(baseTokenUrl.indexOf("/") + 2, baseTokenUrl.indexOf(".")); - baseTokenUrl = baseTokenUrl.replace(providersubdomain, subdomain); + if (subdomain != null && !subdomain.isEmpty()) { + try { + String providersubdomain = + baseTokenUrl.substring(baseTokenUrl.indexOf("/") + 2, baseTokenUrl.indexOf(".")); + 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); + } } + var destination = OAuth2DestinationBuilder.forTargetUrl(sdmCredentials.getUrl()) .withTokenEndpoint(baseTokenUrl) @@ -92,26 +191,60 @@ public String offboardRepository(String subdomain) { builder.maxConnectionsPerRoute(SDMConstants.MAX_CONNECTIONS_PER_ROUTE); builder.maxConnectionsTotal(SDMConstants.MAX_CONNECTIONS_TOTAL); DefaultHttpClientFactory factory = builder.build(); - HttpClient httpClient = factory.createHttpClient(destination); + HttpClient httpClient; + try { + httpClient = factory.createHttpClient(destination); + if (httpClient == null) { + logger.error("Failed to create HTTP client."); + throw new ServiceException("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); + } + String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/"; HttpGet getRepos = new HttpGet(sdmUrl); String repoId = ""; try (var response = (CloseableHttpResponse) httpClient.execute(getRepos)) { - repoId = getRepositoryId(EntityUtils.toString(response.getEntity())); + String responseString = EntityUtils.toString(response.getEntity()); + repoId = getRepositoryId(responseString); + if (repoId == null || repoId.isEmpty()) { + logger.error("Repository ID not found"); + return "Repository with ID " + SDMConstants.REPOSITORY_ID + " not found."; + } } catch (IOException e) { - logger.error("Error in offboarding repository : " + e.getMessage()); - throw new ServiceException("Error in offboarding ", e.getMessage()); + logger.error("Error while fetching repository ID: " + e.getMessage()); + throw new ServiceException("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); } + sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/" + repoId; HttpDelete offboardingReq = new HttpDelete(sdmUrl); - // Set the content type of the request offboardingReq.setHeader("Content-Type", "application/json"); try (var response = (CloseableHttpResponse) httpClient.execute(offboardingReq)) { - logger.info("Repository <" + REPOSITORY_ID + "> Offboarded"); - return "Repository <" + REPOSITORY_ID + "> Offboarded"; + int statusCode = response.getStatusLine().getStatusCode(); + String responseString = EntityUtils.toString(response.getEntity()); + + if (statusCode != 200) { // Failed to offboard + if (statusCode == 404) { // Exception isn't thrown in case of missing repository + logger.warn("Repository with ID " + SDMConstants.REPOSITORY_ID + " not found."); + return "Repository with ID " + SDMConstants.REPOSITORY_ID + " not found."; + } + logger.error("Failed to offboard repository : " + responseString); + throw new ServiceException("Failed to offboard repository.", responseString); + } + + logger.info("Repository " + repoId + " Offboarded"); + return "Repository " + repoId + " Offboarded"; } catch (IOException e) { - logger.error("Error in offboarding repository : " + e.getMessage()); - throw new ServiceException("Error in offboarding ", e.getMessage()); + logger.error("Error while offboarding repository: " + e.getMessage()); + throw new ServiceException("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); } } @@ -119,9 +252,15 @@ private String getRepositoryId(String jsonString) { ObjectMapper objectMapper = new ObjectMapper(); try { JsonNode rootNode = objectMapper.readTree(jsonString); - JsonNode repoInfos = rootNode.path("repoAndConnectionInfos"); + JsonNode repoInfosNode = rootNode.path("repoAndConnectionInfos"); + + List repoInfos = new ArrayList<>(); + if (repoInfosNode.isArray()) { + repoInfosNode.forEach(repoInfos::add); + } else if (!repoInfosNode.isMissingNode() && !repoInfosNode.isNull()) { + repoInfos.add(repoInfosNode); // wrap single object in a list + } - // Iterate through the array to find the correct externalId and retrieve the id for (JsonNode repoInfo : repoInfos) { JsonNode repository = repoInfo.path("repository"); if (repository.path("externalId").asText().equals(SDMConstants.REPOSITORY_ID)) { @@ -129,7 +268,7 @@ private String getRepositoryId(String jsonString) { } } } catch (Exception e) { - throw new ServiceException(String.format(e.getMessage())); + throw new ServiceException("Failed to parse repository response", e); } return null; } diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java b/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java index dc634b4b3..e1a63aecc 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java @@ -145,22 +145,30 @@ private void formResponse( String error = ""; try { String responseString = EntityUtils.toString(response.getEntity()); - JSONObject jsonResponse = new JSONObject(responseString); int responseCode = response.getStatusLine().getStatusCode(); + if (responseCode == 201 || responseCode == 200) { - JSONObject succinctProperties = jsonResponse.getJSONObject("succinctProperties"); status = "success"; + JSONObject jsonResponse = new JSONObject(responseString); + JSONObject succinctProperties = jsonResponse.getJSONObject("succinctProperties"); objectId = succinctProperties.getString("cmis:objectId"); } else { - String message = jsonResponse.getString("message"); - if (responseCode == 409 - && "Malware Service Exception: Virus found in the file!".equals(message)) { - status = "virus"; - } else if (responseCode == 409) { - status = "duplicate"; - } else if (responseCode == 403) { + if (responseCode == 409) { + JSONObject jsonResponse = new JSONObject(responseString); + String message = jsonResponse.getString("message"); + JSONObject succinctProperties = jsonResponse.getJSONObject("succinctProperties"); + objectId = succinctProperties.getString("cmis:objectId"); + if ("Malware Service Exception: Virus found in the file!".equals(message)) { + status = "virus"; + } else { + status = "duplicate"; + } + } else if ((responseCode == 403) + && (responseString.equals("User does not have required scope"))) { status = "unauthorized"; } else { + JSONObject jsonResponse = new JSONObject(responseString); + String message = jsonResponse.getString("message"); status = "fail"; error = message; } @@ -173,8 +181,8 @@ private void formResponse( if (!objectId.isEmpty()) { finalResponse.put("objectId", objectId); } - } catch (IOException e) { - throw new ServiceException(SDMConstants.getGenericError("upload")); + } catch (Exception e) { + throw new ServiceException(e.getMessage()); } } diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java index 9f250eaf5..e289f4702 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java @@ -172,8 +172,11 @@ public void testOnboardRepository_failure() throws UnsupportedEncodingException, JsonProcessingException, IOException { // Arrange SDMCredentials sdmCredentials = new SDMCredentials(); - sdmCredentials.setUrl("https://example.com/"); - when(tokenHandler.getSDMCredentials()).thenReturn(mockCredentials); + sdmCredentials.setBaseTokenUrl("https://subdomain.example.com/oauth/token"); + sdmCredentials.setClientId("clientID"); + sdmCredentials.setClientSecret("clientSecret"); + sdmCredentials.setUrl("url"); + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); when(tokenHandler.getHttpClient(any(), any(), any(), eq("TECHNICAL_CREDENTIALS_FLOW"))) .thenReturn(httpClient); Repository repository = new Repository(); @@ -241,7 +244,7 @@ public void testOffboardRepository_success() throws Exception { String result = sdmAdminService.offboardRepository(subdomain); assertNotNull(result); - assertEquals("Repository Offboarded", result); + assertEquals("Repository 123 Offboarded", result); verify(httpClient, atLeastOnce()).execute(any()); } @@ -294,7 +297,7 @@ public void testOffboardRepository_subdomainnull() throws Exception { String result = sdmAdminService.offboardRepository(subdomain); assertNotNull(result); - assertEquals("Repository Offboarded", result); + assertEquals("Repository with ID repoid not found.", result); verify(httpClient, atLeastOnce()).execute(any()); } @@ -348,7 +351,7 @@ public void testOffboardRepository_subdomainempty() throws Exception { String result = sdmAdminService.offboardRepository(subdomain); assertNotNull(result); - assertEquals("Repository Offboarded", result); + assertEquals("Repository with ID repoid not found.", result); verify(httpClient, atLeastOnce()).execute(any()); } @@ -372,8 +375,7 @@ public void testOffboardRepository_getRequestFails_throwsException() throws Exce () -> { sdmAdminService.offboardRepository(subdomain); }); - - assertTrue(exception.getMessage().contains("Error in offboarding")); + assertTrue(exception.getMessage().contains("Error while fetching repository ID.")); } @Test @@ -424,7 +426,7 @@ public void testOffboardRepository_deleteRequestFails_throwsException() throws E sdmAdminService.offboardRepository(subdomain); }); - assertTrue(exception.getMessage().contains("Error in offboarding")); + assertTrue(exception.getMessage().contains("Error while offboarding repository.")); } @Test @@ -469,10 +471,6 @@ public void testOffboardRepository_invalidRepo_throwsException() throws Exceptio sdmAdminService.offboardRepository(subdomain); }); - assertTrue( - exception - .getMessage() - .contains( - "Unrecognized token 'repoid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')")); + assertTrue(exception.getMessage().contains("Unexpected error while fetching repository ID.")); } } diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java index 58b83fe9d..ecb327833 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java @@ -608,7 +608,8 @@ public void testCreateDocument() throws IOException { @Test public void testCreateDocumentFailDuplicate() throws IOException { - String mockResponseBody = "{\"message\": \"Duplicate document found\"}"; + String mockResponseBody = + "{\"message\": \"Duplicate document found\", \"succinctProperties\": {\"cmis:objectId\": \"objectId\"}}"; CmisDocument cmisDocument = new CmisDocument(); cmisDocument.setFileName("sample.pdf"); cmisDocument.setAttachmentId("attachmentId"); @@ -640,6 +641,7 @@ public void testCreateDocumentFailDuplicate() throws IOException { expectedResponse.put("name", "sample.pdf"); expectedResponse.put("id", "attachmentId"); expectedResponse.put("message", ""); + expectedResponse.put("objectId", "objectId"); expectedResponse.put("status", "duplicate"); assertEquals(expectedResponse.toString(), actualResponse.toString()); } @@ -647,7 +649,7 @@ public void testCreateDocumentFailDuplicate() throws IOException { @Test public void testCreateDocumentFailVirus() throws IOException { String mockResponseBody = - "{\"message\": \"Malware Service Exception: Virus found in the file!\"}"; + "{\"succinctProperties\": {\"cmis:objectId\": \"objectId\"}, \"message\": \"Malware Service Exception: Virus found in the file!\"}"; CmisDocument cmisDocument = new CmisDocument(); cmisDocument.setFileName("sample.pdf"); @@ -681,6 +683,7 @@ public void testCreateDocumentFailVirus() throws IOException { expectedResponse.put("name", "sample.pdf"); expectedResponse.put("id", "attachmentId"); expectedResponse.put("message", ""); + expectedResponse.put("objectId", "objectId"); expectedResponse.put("status", "virus"); assertEquals(expectedResponse.toString(), actualResponse.toString()); }