From a4e7f6808f85388d9974d8c8d7206e4d2e9bd0c7 Mon Sep 17 00:00:00 2001 From: rudra-superrr Date: Thu, 1 Jun 2023 15:56:38 +0530 Subject: [PATCH] feat(REST): New endpoint split components Signed-off-by: Muhammad Ali --- .../db/ComponentDatabaseHandler.java | 47 +++--- .../src/docs/asciidoc/components.adoc | 34 ++++ .../component/ComponentController.java | 32 ++++ .../component/Sw360ComponentService.java | 33 ++++ .../restdocs/ComponentSpecTest.java | 156 +++++++++++++++++- 5 files changed, 277 insertions(+), 25 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java index 9ae1156c06..a3f7a1ca67 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java @@ -944,29 +944,30 @@ private void mergeAttachments(Component mergeSelection, Component mergeTarget, C if (mergeTarget.getAttachments() == null) { mergeTarget.setAttachments(new HashSet<>()); } - - Set attachmentIdsSelected = mergeSelection.getAttachments().stream() - .map(Attachment::getAttachmentContentId).collect(Collectors.toSet()); - // add new attachments from source - Set attachmentsToAdd = new HashSet<>(); - mergeSource.getAttachments().forEach(a -> { - if (attachmentIdsSelected.contains(a.getAttachmentContentId())) { - attachmentsToAdd.add(a); - } - }); - // remove moved attachments in source - attachmentsToAdd.forEach(a -> { - mergeTarget.addToAttachments(a); - mergeSource.getAttachments().remove(a); - }); - // delete unchosen attachments from target - Set attachmentsToDelete = new HashSet<>(); - mergeTarget.getAttachments().forEach(a -> { - if (!attachmentIdsSelected.contains(a.getAttachmentContentId())) { - attachmentsToDelete.add(a); - } - }); - mergeTarget.getAttachments().removeAll(attachmentsToDelete); + if (mergeSelection.getAttachments() != null) { + Set attachmentIdsSelected = mergeSelection.getAttachments().stream() + .map(Attachment::getAttachmentContentId).collect(Collectors.toSet()); + // add new attachments from source + Set attachmentsToAdd = new HashSet<>(); + mergeSource.getAttachments().forEach(a -> { + if (attachmentIdsSelected.contains(a.getAttachmentContentId())) { + attachmentsToAdd.add(a); + } + }); + // remove moved attachments in source + attachmentsToAdd.forEach(a -> { + mergeTarget.addToAttachments(a); + mergeSource.getAttachments().remove(a); + }); + // delete unchosen attachments from target + Set attachmentsToDelete = new HashSet<>(); + mergeTarget.getAttachments().forEach(a -> { + if (!attachmentIdsSelected.contains(a.getAttachmentContentId())) { + attachmentsToDelete.add(a); + } + }); + mergeTarget.getAttachments().removeAll(attachmentsToDelete); + } } private void transferReleases(Set releaseIds, Component mergeTarget, Component mergeSource) throws SW360Exception { diff --git a/rest/resource-server/src/docs/asciidoc/components.adoc b/rest/resource-server/src/docs/asciidoc/components.adoc index e8d93fd077..e1ce5964f4 100644 --- a/rest/resource-server/src/docs/asciidoc/components.adoc +++ b/rest/resource-server/src/docs/asciidoc/components.adoc @@ -313,6 +313,40 @@ include::{snippets}/should_document_update_component/http-response.adoc[] include::{snippets}/should_document_update_component/links.adoc[] +[[resources-components-update]] +==== Merge a component + +A `PATCH` request is used to merge two components + +===== Response structure +include::{snippets}/should_document_merge_components/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_merge_components/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_merge_components/http-response.adoc[] + +===== Links +include::{snippets}/should_document_merge_components/links.adoc[] + +[[resources-components-update]] +==== Split a component + +A `PATCH` request is used to split two components + +===== Response structure +include::{snippets}/should_document_split_components/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_split_components/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_split_components/http-response.adoc[] + +===== Links +include::{snippets}/should_document_split_components/links.adoc[] + [[resources-components-delete]] ==== Delete a component diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java index 4eb57d573c..c06a876ed5 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java @@ -696,4 +696,36 @@ private ImportBomRequestPreparation handleImportBomRequestPreparation(ImportBomR } return importBomRequestPreparationResponse; } + + @PreAuthorize("hasAuthority('WRITE')") + @RequestMapping(value = COMPONENTS_URL + "/mergecomponents", method = RequestMethod.PATCH) + public ResponseEntity mergeComponents( + @RequestParam(value = "mergeTargetId", required = true) String mergeTargetId, + @RequestParam(value = "mergeSourceId", required = true) String mergeSourceId, + @RequestBody Component mergeSelection ) throws TException { + + + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + + // perform the real merge, update merge target and delete merge sources + RequestStatus requestStatus = componentService.mergeComponents(mergeTargetId, mergeSourceId, mergeSelection, sw360User); + + return new ResponseEntity<>(requestStatus, HttpStatus.OK); + } + + @PreAuthorize("hasAuthority('WRITE')") + @RequestMapping(value = COMPONENTS_URL + "/splitComponents", method = RequestMethod.PATCH) + public ResponseEntity splitComponents( + @RequestBody Map componentMap) throws TException { + + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + + Component srcComponent = componentMap.get("srcComponent"); + Component targetComponent = componentMap.get("targetComponent"); + + // perform the real merge, update merge target and delete merge source + RequestStatus requestStatus = componentService.splitComponents(srcComponent, targetComponent, sw360User); + + return new ResponseEntity<>(requestStatus, HttpStatus.OK); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java index b13a841e30..a858678617 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java @@ -275,4 +275,37 @@ public ImportBomRequestPreparation prepareImportSBOM(User user, String attachmen ComponentService.Iface sw360ComponentClient = getThriftComponentClient(); return sw360ComponentClient.prepareImportBom(user, attachmentContentId); } + + public RequestStatus mergeComponents(String componentTargetId, String componentSourceId, Component componentSelection, User user) throws TException { + ComponentService.Iface sw360ComponentClient = getThriftComponentClient(); + RequestStatus requestStatus; + requestStatus = sw360ComponentClient.mergeComponents(componentTargetId, componentSourceId, componentSelection, user); + + if (requestStatus == RequestStatus.IN_USE) { + throw new HttpMessageNotReadableException("Component already in use."); + } else if (requestStatus == RequestStatus.FAILURE) { + throw new HttpMessageNotReadableException("Cannot merge these components"); + } else if (requestStatus == RequestStatus.ACCESS_DENIED) { + throw new RuntimeException("Access denied"); + } + + return requestStatus; + } + + + public RequestStatus splitComponents(Component srcComponent, Component targetComponent, User sw360User) throws TException { + ComponentService.Iface sw360ComponentClient = getThriftComponentClient(); + RequestStatus requestStatus; + requestStatus = sw360ComponentClient.splitComponent(srcComponent, targetComponent, sw360User); + + if (requestStatus == RequestStatus.IN_USE) { + throw new HttpMessageNotReadableException("Component already in use."); + } else if (requestStatus == RequestStatus.FAILURE) { + throw new HttpMessageNotReadableException("Cannot split these components"); + } else if (requestStatus == RequestStatus.ACCESS_DENIED) { + throw new RuntimeException("Access denied...!"); + } + + return requestStatus; + } } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java index b8d24bd41b..59b01f21c9 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java @@ -100,6 +100,8 @@ public class ComponentSpecTest extends TestRestDocsSpecBase { private Component angularComponent; + private Component angularTargetComponent; + private Attachment attachment; private Project project; @@ -108,6 +110,9 @@ public class ComponentSpecTest extends TestRestDocsSpecBase { private Attachment sBOMAttachment; private RequestSummary requestSummary = new RequestSummary(); + private Release release; + private Release release2; + @Before public void before() throws TException, IOException { Set licenseIds = new HashSet<>(); @@ -157,6 +162,9 @@ public void before() throws TException, IOException { Map angularComponentExternalIds = new HashMap<>(); angularComponentExternalIds.put("component-id-key", "1831A3"); angularComponentExternalIds.put("ws-component-id", "[\"123\",\"598752\"]"); + Map angularTargetComponentExternalIds = new HashMap<>(); + angularComponentExternalIds.put("component-id-key", "1831A4"); + angularComponentExternalIds.put("ws-component-id", "[\"123\",\"598753\"]"); angularComponent = new Component(); angularComponent.setId("17653524"); angularComponent.setName("Angular"); @@ -187,7 +195,41 @@ public void before() throws TException, IOException { angularComponent.setHomepage("https://angular.io"); angularComponent.setMainLicenseIds(licenseIds); angularComponent.setDefaultVendorId("vendorId"); + + + angularTargetComponent = new Component(); + angularTargetComponent.setId("87654321"); + angularTargetComponent.setName("Angular"); + angularTargetComponent.setComponentOwner("John"); + angularTargetComponent.setDescription("Angular is a development platform for building mobile and desktop web applications."); + angularTargetComponent.setCreatedOn("2016-12-15"); + angularTargetComponent.setCreatedBy("admin@sw360.org"); + angularTargetComponent.setModifiedBy("admin1@sw360.org"); + angularTargetComponent.setModifiedOn("2016-12-30"); + angularTargetComponent.setSoftwarePlatforms(new HashSet<>(Arrays.asList("Linux"))); + angularTargetComponent.setMainLicenseIds(new HashSet<>(Arrays.asList("123"))); + angularTargetComponent.setSubscribers(new HashSet<>(Arrays.asList("Mari"))); + angularTargetComponent.setWiki("http://wiki.ubuntu.com/"); + angularTargetComponent.setBlog("http://www.javaworld.com/"); + angularTargetComponent.setComponentType(ComponentType.OSS); + angularTargetComponent.setVendorNames(new HashSet<>(Collections.singletonList("Google"))); + angularTargetComponent.setModerators(new HashSet<>(Arrays.asList("admin@sw360.org", "john@sw360.org"))); + angularTargetComponent.setOwnerAccountingUnit("4822"); + angularTargetComponent.setOwnerCountry("DE"); + angularTargetComponent.setOwnerGroup("AA BB 123 GHV2-DE"); + angularTargetComponent.setCategories(ImmutableSet.of("java", "javascript", "sql")); + angularTargetComponent.setLanguages(ImmutableSet.of("EN", "DE")); + angularTargetComponent.setOperatingSystems(ImmutableSet.of("Windows", "Linux")); + angularTargetComponent.setAttachments(attachmentList); + angularTargetComponent.setExternalIds(angularTargetComponentExternalIds); + angularTargetComponent.setMailinglist("test@liferay.com"); + angularTargetComponent.setAdditionalData(Collections.singletonMap("Key", "Value")); + angularTargetComponent.setHomepage("https://angular.io"); + angularTargetComponent.setMainLicenseIds(licenseIds); + angularTargetComponent.setDefaultVendorId("vendorId"); + componentList.add(angularComponent); + componentList.add(angularTargetComponent); componentListByName.add(angularComponent); Component springComponent = new Component(); @@ -257,6 +299,12 @@ public void before() throws TException, IOException { .setComponentType(ComponentType.OSS) .setExternalIds(Collections.singletonMap("component-id-key", "1831A3")) ); + given(this.componentServiceMock.convertToEmbeddedWithExternalIds(eq(angularTargetComponent))).willReturn( + new Component("Angular") + .setId("87654321") + .setComponentType(ComponentType.OSS) + .setExternalIds(Collections.singletonMap("component-id-key", "1831A4")) + ); given(this.componentServiceMock.convertToEmbeddedWithExternalIds(eq(springComponent))).willReturn( new Component("Spring Framework") .setId("678dstzd8") @@ -274,7 +322,7 @@ public void before() throws TException, IOException { given(this.vendorServiceMock.getVendorById("vendorId")).willReturn(vendor); List releaseList = new ArrayList<>(); - Release release = new Release(); + release = new Release(); release.setId("3765276512"); release.setName("Angular 2.3.0"); release.setCpeid("cpe:/a:Google:Angular:2.3.0:"); @@ -286,7 +334,7 @@ public void before() throws TException, IOException { release.setComponentId(springComponent.getId()); releaseList.add(release); - Release release2 = new Release(); + release2 = new Release(); release2.setId("3765276512"); release2.setName("Angular 2.3.1"); release2.setCpeid("cpe:/a:Google:Angular:2.3.1:"); @@ -840,6 +888,110 @@ public void should_document_update_component() throws Exception { .andDo(documentComponentProperties()); } + @Test + public void should_document_merge_components() throws Exception { + + + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(patch("/api/components/mergecomponents") + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(angularTargetComponent)) + .header("Authorization", "Bearer " + accessToken) + .param("mergeTargetId", "87654321") + .param("mergeSourceId", "17653524") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + requestParameters( + parameterWithName("mergeSourceId").description("Id of a source component to merge"), + parameterWithName("mergeTargetId").description("Id of a target component to merge") + ), + requestFields( + fieldWithPath("id").description("The Id of the component"), + fieldWithPath("subscribers").description("The subscribers of component"), + fieldWithPath("ownerAccountingUnit").description("The owner accounting unit of the component"), + subsectionWithPath("externalIds").description("When components are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"), + subsectionWithPath("additionalData").description("A place to store additional data used by external tools"), + fieldWithPath("mainLicenseIds").description("The Main License Ids of component"), + fieldWithPath("languages").description("The language of the component"), + fieldWithPath("softwarePlatforms").description("The Software Platforms of component"), + fieldWithPath("operatingSystems").description("The OS on which the component operates"), + fieldWithPath("wiki").description("The wiki of component"), + fieldWithPath("blog").description("The blog of component"), + fieldWithPath("homepage").description("The homepage url of the component"), + fieldWithPath("modifiedOn").description("The date the component was modified"), + + fieldWithPath("name").description("The updated name of the component"), + fieldWithPath("type").description("The updated name of the component"), + fieldWithPath("createdOn").description("The date the component was created"), + fieldWithPath("componentOwner").description("The owner name of the component"), + fieldWithPath("ownerGroup").description("The owner group of the component"), + fieldWithPath("ownerCountry").description("The owner country of the component"), + fieldWithPath("visbility").description("The visibility type of the component"), + fieldWithPath("defaultVendorId").description("Default vendor id of component"), + fieldWithPath("categories").description("The component categories"), + fieldWithPath("mailinglist").description("Component mailing lists"), + fieldWithPath("setVisbility").description("The visibility of the component"), + fieldWithPath("setBusinessUnit").description("Whether or not a business unit is set for the component"), + fieldWithPath("vendors").description("The vendors list"), + fieldWithPath("description").description("The updated component description"), + fieldWithPath("componentType").description("The updated component type, possible values are: " + Arrays.asList(ComponentType.values())) + ))); + } + @Test + public void should_document_split_components() throws Exception { + + + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + Component srcComponent = new Component(); + srcComponent.setId("17653524"); + srcComponent.setName("Angular"); + srcComponent.setComponentOwner("John"); + srcComponent.setDescription("Angular is a development platform for building mobile and desktop web applications."); + List releaseList = new ArrayList<>(); + releaseList.add(release); + Release release2 = new Release(); + releaseList.add(release2); + + srcComponent.setReleases(releaseList); + Component targetComponent = new Component(); + targetComponent.setId("87654321"); + targetComponent.setName("Angular"); + targetComponent.setComponentOwner("John"); + targetComponent.setDescription("Angular is a development platform for building mobile and desktop web applications."); + targetComponent.setReleases(releaseList); + Map componentsMap = new HashMap<>(); + componentsMap.put("srcComponent", srcComponent); + componentsMap.put("targetComponent", targetComponent); + + mockMvc.perform(patch("/api/components/splitComponents") + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(componentsMap)) + .header("Authorization", "Bearer " + accessToken) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + requestFields( + fieldWithPath("srcComponent.id").description("The ID of the source component"), + fieldWithPath("srcComponent.name").description("The name of the source component"), + fieldWithPath("srcComponent.description").description("The description of the source component"), + fieldWithPath("srcComponent.type").description("The type of the source component"), + fieldWithPath("srcComponent.componentOwner").description("The owner of the source component"), + fieldWithPath("srcComponent.visbility").description("The visibility of the source component"), + fieldWithPath("srcComponent.setVisbility").description("Flag indicating if the visibility is set"), + fieldWithPath("srcComponent.setBusinessUnit").description("Flag indicating if the business unit is set"), + fieldWithPath("targetComponent.id").description("The ID of the target component"), + fieldWithPath("targetComponent.name").description("The name of the target component"), + fieldWithPath("targetComponent.description").description("The description of the target component"), + fieldWithPath("targetComponent.type").description("The type of the target component"), + fieldWithPath("targetComponent.componentOwner").description("The owner of the target component"), + fieldWithPath("targetComponent.visbility").description("The visibility of the target component"), + fieldWithPath("targetComponent.setVisbility").description("Flag indicating if the visibility is set"), + fieldWithPath("targetComponent.setBusinessUnit").description("Flag indicating if the business unit is set") + + ))); + } + @Test public void should_document_delete_components() throws Exception { String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword);