From 1ab8200fba4cbfe3175daa4d83ee005b748be2ae Mon Sep 17 00:00:00 2001 From: Nikesh kumar Date: Thu, 16 Jan 2025 14:52:28 +0530 Subject: [PATCH] fix(rest): Add license information linking for project releases. Signed-off-by: Nikesh kumar --- .../project/ProjectController.java | 67 ++++++++++ .../project/Sw360ProjectService.java | 116 ++++++++++++++++++ .../restdocs/ProjectSpecTest.java | 14 ++- 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java index 28e125581a..c83d5b7b4f 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java @@ -112,6 +112,7 @@ import org.eclipse.sw360.rest.resourceserver.vulnerability.Sw360VulnerabilityService; import org.eclipse.sw360.rest.resourceserver.vulnerability.VulnerabilityController; import org.jetbrains.annotations.NotNull; +import org.jose4j.json.internal.json_simple.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.json.GsonJsonParser; import org.springframework.data.domain.Pageable; @@ -130,6 +131,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -203,6 +205,8 @@ public class ProjectController implements RepresentationModelProcessor enumMainlineStateValues = Stream.of(MainlineState.values()) .map(MainlineState::name) .collect(Collectors.toList()); + private static final ImmutableMap RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT = ImmutableMap.builder() + .put("message", "Unauthorized user or empty commit message passed.").build(); @NonNull private final Sw360ProjectService projectService; @@ -3546,4 +3550,67 @@ public ResponseEntity createDuplicateProjectWithDependencyNetwork( return true; }; } + + @Operation( + summary = "Add licenses to linked releases of a project.", + description = "This API adds license information to linked releases of a project by processing the approved CLI attachments for each release. It categorizes releases based on the number of CLI attachments (single, multiple, or none) and updates their main and other licenses accordingly.", + tags = {"Project"}, + parameters = { + @Parameter( + name = "projectId", + description = "The ID of the project whose linked releases need license updates.", + required = true, + example = "12345", + schema = @Schema(type = "string") + ) + }, + responses = { + @ApiResponse( + responseCode = "200", + description = "License information successfully added to linked releases.", + content = @Content( + mediaType = "application/hal+json", + schema = @Schema(type = "object", implementation = JSONObject.class), + examples = @ExampleObject( + value = "{\"message\": \"License information successfully added to linked releases.\" }" + ) + ) + ), + @ApiResponse( + responseCode = "500", + description = "Error occurred while processing license information for linked releases.", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + value = "{\n \"error\": \"Error adding license info to linked releases.\"\n}" + ) + ) + ) + } + ) + @PostMapping(value = PROJECTS_URL + "/{id}/addLinkedRelesesLicenses") + public ResponseEntity addLicenseToLinkedReleases( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String projectId) throws TException, TTransportException, ResourceClassNotFoundException { + try { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project project = projectService.getProjectForUserById(projectId, sw360User); + + if (project == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Project not found."); + } + + RequestStatus requestStatus = projectService.addLicenseToLinkedReleases(projectId, sw360User); + + if (requestStatus == RequestStatus.SUCCESS) { + return ResponseEntity.ok("License information successfully added to linked releases."); + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to add license information to linked releases."); + } + } catch (ResourceNotFoundException e) { + throw new ResourceClassNotFoundException(e.getMessage()); + } catch (SW360Exception sw360Exp) { + throw new RuntimeException(sw360Exp.getWhy()); + } + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java index d240064e75..2a97caaae4 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java @@ -74,6 +74,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; @@ -1543,4 +1544,119 @@ public List serveLinkedReleasesInDependencyNetworkByIndexPath(Strin ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); return sw360ProjectClient.getReleaseLinksOfProjectNetWorkByIndexPath(projectId, indexPath, sw360User); } + + public RequestStatus addLicenseToLinkedReleases(String projectId, User sw360User) + throws TTransportException, TException { + + try { + ProjectService.Iface projectClient = getThriftProjectClient(); + LicenseInfoService.Iface licenseInfoClient = new ThriftClients().makeLicenseInfoClient(); + ComponentService.Iface componentClient = new ThriftClients().makeComponentClient(); + + Project project = projectClient.getProjectById(projectId, sw360User); + if (project == null) { + log.error("Requested Project Not Found: " + projectId); + throw new ResourceNotFoundException("Project not available"); + } + + Set releaseIds = CommonUtils.getNullToEmptyKeyset(project.getReleaseIdToUsage()); + List releasesToUpdate = new ArrayList<>(); + + for (String releaseId : releaseIds) { + Release release = componentClient.getReleaseById(releaseId, sw360User); + if (release == null) { + log.error("Release with ID " + releaseId + " not found."); + continue; + } + + Set originalMainLicenses = release.getMainLicenseIds() == null ? new HashSet<>() : new HashSet<>(release.getMainLicenseIds()); + Set originalOtherLicenses = release.getOtherLicenseIds() == null ? new HashSet<>() : new HashSet<>(release.getOtherLicenseIds()); + + List approvedCliAttachments = SW360Utils.getApprovedClxAttachmentForRelease(release); + if (approvedCliAttachments.isEmpty()) { + approvedCliAttachments = SW360Utils.getClxAttachmentForRelease(release); + } + + boolean updated = false; + if (!approvedCliAttachments.isEmpty()) { + updated = processSingleAttachment(approvedCliAttachments.get(0), release, licenseInfoClient, sw360User); + } + + // Ensure existing licenses are retained if no attachments were processed + if (!updated && (!originalMainLicenses.equals(release.getMainLicenseIds()) || !originalOtherLicenses.equals(release.getOtherLicenseIds()))) { + updated = true; + } + + if (updated) { + releasesToUpdate.add(release); + } + } + + //Update all releases that have changes + for (Release release : releasesToUpdate) { + log.info("Updating release: {}", release.getId()); + componentClient.updateRelease(release, sw360User); + } + + return RequestStatus.SUCCESS; + + } catch (SW360Exception sw360Exp) { + if (sw360Exp.getErrorCode() == 404) { + throw new ResourceNotFoundException("Requested Project Not Found"); + } else if (sw360Exp.getErrorCode() == 403) { + throw new AccessDeniedException("Project or its linked releases are restricted and not accessible."); + } + throw new RuntimeException("SW360 API error: " + sw360Exp.getMessage(), sw360Exp); + } catch (Exception e) { + throw new RuntimeException("Error processing linked releases for project: " + projectId, e); + } + } + + + private boolean processSingleAttachment(Attachment attachment, Release release, + LicenseInfoService.Iface licenseInfoClient, User sw360User) throws TTransportException, TException { + + String attachmentName = attachment.getFilename(); + Set mainLicenses = new HashSet<>(); + Set otherLicenses = new HashSet<>(); + + List licenseInfoResults = licenseInfoClient.getLicenseInfoForAttachment(release, + attachment.getAttachmentContentId(), true, sw360User); + + if (attachmentName.endsWith(SW360Constants.RDF_FILE_EXTENSION)) { + licenseInfoResults.forEach(result -> { + if (result.getLicenseInfo() != null) { + mainLicenses.addAll(result.getLicenseInfo().getConcludedLicenseIds()); + otherLicenses.addAll(result.getLicenseInfo().getLicenseNamesWithTexts().stream() + .map(LicenseNameWithText::getLicenseName).collect(Collectors.toSet())); + } + }); + otherLicenses.removeAll(mainLicenses); + } else if (attachmentName.endsWith(SW360Constants.XML_FILE_EXTENSION)) { + licenseInfoResults.forEach(result -> { + if (result.getLicenseInfo() != null) { + result.getLicenseInfo().getLicenseNamesWithTexts().forEach(license -> { + if (SW360Constants.LICENSE_TYPE_GLOBAL.equals(license.getType())) { + mainLicenses.add(license.getLicenseName()); + } else { + otherLicenses.add(license.getLicenseName()); + } + }); + } + }); + } + + boolean isUpdated = !mainLicenses.equals(release.getMainLicenseIds()) || !otherLicenses.equals(release.getOtherLicenseIds()); + + if (isUpdated) { + log.debug("Updating licenses for release: {}", release.getId()); + release.setMainLicenseIds(mainLicenses); + release.setOtherLicenseIds(otherLicenses); + } else { + log.debug("No changes detected for release: {}", release.getId()); + } + + return isUpdated; + } + } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java index 80ccad138a..c12738da57 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java @@ -73,6 +73,8 @@ import org.eclipse.sw360.rest.resourceserver.user.Sw360UserService; import org.eclipse.sw360.rest.resourceserver.vulnerability.Sw360VulnerabilityService; import org.hamcrest.Matchers; +import org.jose4j.json.internal.json_simple.JSONArray; +import org.jose4j.json.internal.json_simple.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -1659,7 +1661,6 @@ public void should_document_update_project_vulnerabilities() throws Exception { responseFields( subsectionWithPath("_embedded.sw360:vulnerabilityDTOes.[]projectRelevance").description("The relevance of project of the vulnerability, possible values are: " + Arrays.asList(VulnerabilityRatingForProject.values())), subsectionWithPath("_embedded.sw360:vulnerabilityDTOes.[]intReleaseId").description("The release id"), - // subsectionWithPath("_embedded.sw360:vulnerabilityDTOes.[]packageIds").description("The list of package IDs linked to the vulnerability."), subsectionWithPath("_embedded.sw360:vulnerabilityDTOes.[]comment").description("Any message to add while updating project vulnerabilities"), subsectionWithPath("_embedded.sw360:vulnerabilityDTOes.[]projectAction").description("The action of vulnerability"), subsectionWithPath("_embedded.sw360:vulnerabilityDTOes").description("An array of <>"), @@ -3282,4 +3283,15 @@ public void should_document_get_package_by_project_id() throws Exception { .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) .accept(MediaTypes.HAL_JSON)).andExpect(status().isOk()); } + + @Test + public void should_add_license_to_linked_releases() throws Exception { + String projectId = "1234567"; + when(projectServiceMock.addLicenseToLinkedReleases(eq(projectId), any(User.class))).thenReturn(RequestStatus.SUCCESS); + + MockHttpServletRequestBuilder requestBuilder = post("/api/projects/" + projectId + "/addLinkedRelesesLicenses") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)); + this.mockMvc.perform(requestBuilder).andExpect(status().isOk()).andDo(this.documentationHandler.document()); + } }