From 451f3324ba4795364dbdaaa920b390564b3ad4f2 Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Tue, 5 Sep 2023 09:31:09 +0700 Subject: [PATCH] feat(Release): Get linked releases of release --- .../src/docs/asciidoc/releases.adoc | 34 ++++++++ .../core/JacksonCustomizations.java | 13 ++- .../core/RestControllerHelper.java | 12 +++ .../release/ReleaseController.java | 27 +++++++ .../release/Sw360ReleaseService.java | 27 +++++++ .../restdocs/ReleaseSpecTest.java | 79 ++++++++++++++++++- 6 files changed, 190 insertions(+), 2 deletions(-) diff --git a/rest/resource-server/src/docs/asciidoc/releases.adoc b/rest/resource-server/src/docs/asciidoc/releases.adoc index d35c1264ab..6525ace930 100644 --- a/rest/resource-server/src/docs/asciidoc/releases.adoc +++ b/rest/resource-server/src/docs/asciidoc/releases.adoc @@ -516,3 +516,37 @@ include::{snippets}/should_document_load_spdx_licenses_info_from_isr/http-respon ===== Example response for Component license information (CLX or CLI) type include::{snippets}/should_document_load_spdx_licenses_info_from_clx_or_cli/http-response.adoc[] + +[[resources-get-linked-releases-of-release]] +==== Get direct linked releases + +A `GET` request will get direct linked releases of a single release + +===== Response structure +include::{snippets}/should_document_get_direct_linked_releases/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_get_direct_linked_releases/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_direct_linked_releases/http-response.adoc[] + +===== Links +include::{snippets}/should_document_get_direct_linked_releases/links.adoc[] + +[[resources-get-linked-releases-of-release-transitive]] +==== Get linked releases (transitive) + +A `GET` request will get linked releases of a single release (transitive) + +===== Response structure +include::{snippets}/should_document_get_linked_releases_transitively/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_get_linked_releases_transitively/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_linked_releases_transitively/http-response.adoc[] + +===== Links +include::{snippets}/should_document_get_linked_releases_transitively/links.adoc[] \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index 3add36f2d6..eba9d89067 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -697,7 +697,18 @@ static abstract class ReleaseMixin extends Release { "setLicenseNames", "setAccessible", "setId", - "setClearingReport" + "setClearingReport", + "hasSubreleases", + "accessible", + "layer", + "index", + "setIndex", + "releaseWithSameComponentSize", + "setReleaseWithSameComponent", + "setLayer", + "setDefaultValue", + "setProjectId", + "setReleaseMainLineState" }) static abstract class ReleaseLinkMixin { diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index ad60e3a580..c5a1c61dc5 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -31,6 +31,7 @@ import org.eclipse.sw360.datahandler.thrift.ProjectReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.Quadratic; import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; @@ -1311,4 +1312,15 @@ public void addEmbeddedCotsDetails(HalResource halResource, Release release) { addEmbeddedFields("sw360:cotsDetails", cotsDetailsHalResource, halResource); } } + + public ReleaseLink convertToReleaseLink(Release release, ReleaseRelationship relationship) { + ReleaseLink releaseLink = new ReleaseLink(); + releaseLink.setId(release.getId()); + releaseLink.setClearingState(release.getClearingState()); + releaseLink.setLicenseIds(release.getMainLicenseIds()); + releaseLink.setName(release.getName()); + releaseLink.setVersion(release.getVersion()); + releaseLink.setReleaseRelationship(relationship); + return releaseLink; + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java index 86ca62d477..fca78bd548 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java @@ -853,6 +853,33 @@ public ResponseEntity loadSpdxLicensesInfo( return new ResponseEntity<>(responseBody, HttpStatus.OK); } + @GetMapping(value = RELEASES_URL + "/{id}/releases") + public ResponseEntity>> getLinkedReleases( + @PathVariable("id") String id, + @RequestParam(value = "transitive", required = false, defaultValue = "false") boolean transitive + ) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Release sw360Release = releaseService.getReleaseForUserById(id, sw360User); + Map releaseRelationshipMap = !CommonUtils.isNullOrEmptyMap(sw360Release.getReleaseIdToRelationship()) + ? sw360Release.getReleaseIdToRelationship() + : new HashMap<>(); + Set releaseIdsInBranch = new HashSet<>(); + + final List> linkedReleaseResources = releaseRelationshipMap.entrySet().stream() + .map(item -> wrapTException(() -> { + final Release releaseById = releaseService.getReleaseForUserById(item.getKey(), sw360User); + final ReleaseLink embeddedReleaseLink = restControllerHelper.convertToReleaseLink(releaseById, item.getValue()); + final HalResource releaseResource = new HalResource<>(embeddedReleaseLink); + if (transitive) { + releaseService.addEmbeddedLinkedRelease(releaseById, sw360User, releaseResource, releaseIdsInBranch); + } + return releaseResource; + })).collect(Collectors.toList()); + + CollectionModel> collectionModel = CollectionModel.of(linkedReleaseResources); + return new ResponseEntity<>(collectionModel, HttpStatus.OK); + } + private RequestStatus linkOrUnlinkPackages(String id, Set packagesInRequestBody, boolean link) throws URISyntaxException, TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java index 0bfd2fdfcb..679fb7e6bb 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java @@ -28,6 +28,7 @@ import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.ThriftClients; +import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; import org.eclipse.sw360.datahandler.thrift.components.*; @@ -38,6 +39,7 @@ import org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer; import org.eclipse.sw360.rest.resourceserver.attachment.Sw360AttachmentService; import org.eclipse.sw360.rest.resourceserver.core.AwareOfRestServices; +import org.eclipse.sw360.rest.resourceserver.core.HalResource; import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; import org.eclipse.sw360.rest.resourceserver.project.Sw360ProjectService; import org.eclipse.sw360.rest.resourceserver.license.Sw360LicenseService; @@ -47,6 +49,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; @@ -55,6 +58,7 @@ import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyString; import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -683,4 +687,27 @@ public RequestStatus triggerReportGenerationFossology(String releaseId, User use FossologyService.Iface fossologyClient = getThriftFossologyClient(); return fossologyClient.triggerReportGenerationFossology(releaseId, user); } + + public void addEmbeddedLinkedRelease(Release sw360Release, User sw360User, HalResource releaseResource, Set releaseIdsInBranch) { + releaseIdsInBranch.add(sw360Release.getId()); + Map releaseIdToRelationship = sw360Release.getReleaseIdToRelationship(); + if (releaseIdToRelationship != null) { + releaseIdToRelationship.forEach((key, value) -> wrapTException(() -> { + if (releaseIdsInBranch.contains(key)) { + return; + } + + Release linkedRelease = getReleaseForUserById(key, sw360User); + ReleaseLink embeddedLinkedRelease = convertToEmbeddedLinkedRelease(linkedRelease, value); + HalResource halLinkedRelease = new HalResource<>(embeddedLinkedRelease); + addEmbeddedLinkedRelease(linkedRelease, sw360User, halLinkedRelease, releaseIdsInBranch); + releaseResource.addEmbeddedResource("sw360:releaseLinks", halLinkedRelease); + })); + } + releaseIdsInBranch.remove(sw360Release.getId()); + } + + public ReleaseLink convertToEmbeddedLinkedRelease(Release release, ReleaseRelationship relationship) { + return rch.convertToReleaseLink(release, relationship); + } } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java index 0f5f7d46ef..05ff19662b 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java @@ -13,6 +13,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.doCallRealMethod; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -54,6 +55,7 @@ import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStatus; import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStep; import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.packages.Package; import org.eclipse.sw360.datahandler.thrift.packages.PackageManager; @@ -68,6 +70,7 @@ import org.eclipse.sw360.rest.resourceserver.TestHelper; import org.eclipse.sw360.rest.resourceserver.attachment.AttachmentInfo; import org.eclipse.sw360.rest.resourceserver.attachment.Sw360AttachmentService; +import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; import org.eclipse.sw360.rest.resourceserver.license.Sw360LicenseService; import org.eclipse.sw360.rest.resourceserver.packages.SW360PackageService; import org.eclipse.sw360.rest.resourceserver.licenseinfo.Sw360LicenseInfoService; @@ -130,7 +133,7 @@ public class ReleaseSpecTest extends TestRestDocsSpecBase { @MockBean private Sw360LicenseInfoService licenseInfoMockService; - private Release release, release3, releaseTest; + private Release release, release3, releaseTest, release5; private Attachment attachment; Component component; private Project project; @@ -325,6 +328,8 @@ public void before() throws TException, IOException { release2.setClearingInformation(clearingInfo); release2.setCotsDetails(cotsDetails); release2.setEccInformation(eccInformation); + release2.setMainLicenseIds(Set.of("MIT", "GPL")); + release2.setOtherLicenseIds(Set.of("MIT")); releaseList.add(release2); Package package2 = new Package() @@ -355,6 +360,31 @@ public void before() throws TException, IOException { release3.setCreatedOn("2016-12-18"); release3.setCreatedBy("admin@sw360.org"); release3.setComponentId("1234"); + release3.setMainLicenseIds(Set.of("MIT", "GPL 2+")); + release3.setClearingState(ClearingState.APPROVED); + release3.setMainlineState(MainlineState.MAINLINE); + release3.setOtherLicenseIds(Set.of("MIT")); + + Release release4 = new Release(); + release4.setId("90876"); + release4.setName("Numpy"); + release4.setVersion("1.19.5"); + release4.setMainLicenseIds(Set.of("MIT")); + release4.setClearingState(ClearingState.APPROVED); + release4.setOtherLicenseIds(Collections.emptySet()); + + ReleaseLink releaseLink4 = new ReleaseLink(); + releaseLink4.setId(release4.getId()); + releaseLink4.setName(release4.getName()); + releaseLink4.setVersion(release4.getVersion()); + releaseLink4.setLicenseIds(release4.getMainLicenseIds()); + releaseLink4.setClearingState(release4.getClearingState()); + releaseLink4.setReleaseRelationship(ReleaseRelationship.CODE_SNIPPET); + + release5 = new Release(); + release5.setId("3333333"); + release5.setReleaseIdToRelationship(Map.of(release2.getId(), ReleaseRelationship.DYNAMICALLY_LINKED, release3.getId(), ReleaseRelationship.CODE_SNIPPET)); + Attachment attachment3 = new Attachment(attachment); attachment3.setAttachmentContentId("34535345"); attachment3.setAttachmentType(AttachmentType.SOURCE); @@ -402,6 +432,8 @@ public void before() throws TException, IOException { given(this.releaseServiceMock.getRecentReleases(any())).willReturn(releaseList); given(this.releaseServiceMock.getReleaseSubscriptions(any())).willReturn(releaseList); given(this.releaseServiceMock.getReleaseForUserById(eq(release.getId()), any())).willReturn(release); + given(this.releaseServiceMock.getReleaseForUserById(eq(release2.getId()), any())).willReturn(release2); + given(this.releaseServiceMock.getReleaseForUserById(eq(release5.getId()), any())).willReturn(release5); given(this.releaseServiceMock.getReleaseForUserById(eq(testRelease.getId()), any())).willReturn(testRelease); given(this.releaseServiceMock.getProjectsByRelease(eq(release.getId()), any())).willReturn(projectList); given(this.releaseServiceMock.getUsingComponentsForRelease(eq(release.getId()), any())).willReturn(usedByComponent); @@ -418,6 +450,9 @@ public void before() throws TException, IOException { when(this.releaseServiceMock.createRelease(any(), any())).then(invocation -> new Release("Test Release", "1.0", component.getId()) .setId("1234567890")); + doCallRealMethod().when(this.releaseServiceMock).addEmbeddedLinkedRelease(any(), any(), any(), any()); + given(this.releaseServiceMock.getReleaseForUserById(eq("90876"), any())).willReturn(release4); + given(this.releaseServiceMock.convertToEmbeddedLinkedRelease(any(), any())).willReturn(releaseLink4); given(this.userServiceMock.getUserByEmailOrExternalId("admin@sw360.org")).willReturn( new User("admin@sw360.org", "sw360").setId("123456789")); @@ -1237,6 +1272,48 @@ public void should_document_load_spdx_licenses_info_from_clx_or_cli() throws Exc .andExpect(status().isOk()); } + @Test + public void should_document_get_direct_linked_releases() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(get("/api/releases/" + release5.getId() + "/releases") + .header("Authorization", "Bearer " + accessToken) + .param("transitive", "false") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + requestParameters( + parameterWithName("transitive").description("Get the transitive releases") + ), + links( + linkWithRel("curies").description("Curies are used for online documentation") + ), + responseFields( + subsectionWithPath("_embedded.sw360:releaseLinks").description("An array of <>"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } + + @Test + public void should_document_get_linked_releases_transitively() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(get("/api/releases/" + release5.getId() + "/releases") + .header("Authorization", "Bearer " + accessToken) + .param("transitive", "true") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + requestParameters( + parameterWithName("transitive").description("Get the transitive releases") + ), + links( + linkWithRel("curies").description("Curies are used for online documentation") + ), + responseFields( + subsectionWithPath("_embedded.sw360:releaseLinks").description("An array of <>"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } + public void mockLicensesInfo(AttachmentType attachmentType) throws TException{ Set listAttachment = new HashSet<>(); Attachment attachmentTest = new Attachment();