Skip to content

Commit

Permalink
Merge pull request #1970 from muhammadali9699/feat/api/mergeComponents
Browse files Browse the repository at this point in the history
feat(rest): new endpoint merge components

Reviewed by: eldrin.sanctis@siemens.com
Tested by: eldrin.sanctis@siemens.com
  • Loading branch information
ag4ums authored Jul 4, 2023
2 parents 7134e5f + 662a059 commit 393e471
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -945,29 +945,30 @@ private void mergeAttachments(Component mergeSelection, Component mergeTarget, C
if (mergeTarget.getAttachments() == null) {
mergeTarget.setAttachments(new HashSet<>());
}

Set<String> attachmentIdsSelected = mergeSelection.getAttachments().stream()
.map(Attachment::getAttachmentContentId).collect(Collectors.toSet());
// add new attachments from source
Set<Attachment> 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<Attachment> attachmentsToDelete = new HashSet<>();
mergeTarget.getAttachments().forEach(a -> {
if (!attachmentIdsSelected.contains(a.getAttachmentContentId())) {
attachmentsToDelete.add(a);
}
});
mergeTarget.getAttachments().removeAll(attachmentsToDelete);
if (mergeSelection.getAttachments() != null) {
Set<String> attachmentIdsSelected = mergeSelection.getAttachments().stream()
.map(Attachment::getAttachmentContentId).collect(Collectors.toSet());
// add new attachments from source
Set<Attachment> 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<Attachment> attachmentsToDelete = new HashSet<>();
mergeTarget.getAttachments().forEach(a -> {
if (!attachmentIdsSelected.contains(a.getAttachmentContentId())) {
attachmentsToDelete.add(a);
}
});
mergeTarget.getAttachments().removeAll(attachmentsToDelete);
}
}

private void transferReleases(Set<String> releaseIds, Component mergeTarget, Component mergeSource) throws SW360Exception {
Expand Down
18 changes: 18 additions & 0 deletions rest/resource-server/src/docs/asciidoc/components.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,24 @@ 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-delete]]
==== Delete a component

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,4 +697,20 @@ private ImportBomRequestPreparation handleImportBomRequestPreparation(ImportBomR
}
return importBomRequestPreparationResponse;
}

@PreAuthorize("hasAuthority('WRITE')")
@RequestMapping(value = COMPONENTS_URL + "/mergecomponents", method = RequestMethod.PATCH)
public ResponseEntity<RequestStatus> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,21 @@ 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ public class ComponentSpecTest extends TestRestDocsSpecBase {

private Component angularComponent;

private Component angularTargetComponent;

private Attachment attachment;

private Project project;
Expand Down Expand Up @@ -154,6 +156,9 @@ public void before() throws TException, IOException {
Map<String, String> angularComponentExternalIds = new HashMap<>();
angularComponentExternalIds.put("component-id-key", "1831A3");
angularComponentExternalIds.put("ws-component-id", "[\"123\",\"598752\"]");
Map<String, String> 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");
Expand Down Expand Up @@ -184,7 +189,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);

AttachmentDTO attachmentDTO = new AttachmentDTO();
Expand Down Expand Up @@ -277,6 +316,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")
Expand Down Expand Up @@ -862,6 +907,57 @@ 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_delete_components() throws Exception {
String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword);
Expand Down

0 comments on commit 393e471

Please sign in to comment.