From 72f985317a49f6ad5afafd5250d8d5a5e6172ca6 Mon Sep 17 00:00:00 2001 From: "sameed.ahmad" Date: Wed, 15 May 2024 17:40:40 +0530 Subject: [PATCH] feat(importCDX): Add functionality to configure release creation when importing SBOM to an existing project Signed-off-by: sameed.ahmad --- .../sw360/cyclonedx/CycloneDxBOMImporter.java | 69 ++++++++++++++----- .../db/ProjectDatabaseHandler.java | 6 +- .../sw360/projects/ProjectHandler.java | 4 +- .../portlets/projects/ProjectPortlet.java | 3 +- .../src/main/thrift/projects.thrift | 2 +- .../project/ProjectController.java | 8 +-- .../project/Sw360ProjectService.java | 4 +- .../restdocs/ProjectSpecTest.java | 3 +- 8 files changed, 68 insertions(+), 31 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java index 08402379e8..b5b92d1267 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java @@ -13,14 +13,7 @@ import java.io.InputStream; import java.io.StringWriter; import java.nio.charset.Charset; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -155,7 +148,7 @@ private Map> getVcsToComponentMap(Li } @SuppressWarnings("unchecked") - public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user) { + public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user, boolean replacePackageFlag) { RequestSummary requestSummary = new RequestSummary(); Map messageMap = new HashMap<>(); requestSummary.setRequestStatus(RequestStatus.FAILURE); @@ -194,16 +187,15 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a if (!IS_PACKAGE_PORTLET_ENABLED) { vcsToComponentMap.put("", components); - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, replacePackageFlag); } else { vcsToComponentMap = getVcsToComponentMap(components); if (componentsCount == vcsCount) { - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, replacePackageFlag); } else if (componentsCount > vcsCount) { - - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, replacePackageFlag); if (requestSummary.requestStatus.equals(RequestStatus.SUCCESS)) { @@ -366,7 +358,7 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a } public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMetadata, - Map> vcsToComponentMap, String projectId, AttachmentContent attachmentContent) + Map> vcsToComponentMap, String projectId, AttachmentContent attachmentContent, boolean replacePackageFlag) throws SW360Exception { final RequestSummary summary = new RequestSummary(); summary.setRequestStatus(RequestStatus.FAILURE); @@ -420,7 +412,7 @@ public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMeta } if (IS_PACKAGE_PORTLET_ENABLED) { - messageMap = importAllComponentsAsPackages(vcsToComponentMap, project); + messageMap = importAllComponentsAsPackages(vcsToComponentMap, project, replacePackageFlag); } else { messageMap = importAllComponentsAsReleases(vcsToComponentMap, project); } @@ -550,7 +542,7 @@ private Map importAllComponentsAsReleases(Map importAllComponentsAsPackages(Map> vcsToComponentMap, Project project) { + private Map importAllComponentsAsPackages(Map> vcsToComponentMap, Project project, boolean replacePackageFlag) { final var countMap = new HashMap(); final Set duplicateComponents = new HashSet<>(); @@ -559,10 +551,10 @@ private Map importAllComponentsAsPackages(Map invalidReleases = new HashSet<>(); final Set invalidPackages = new HashSet<>(); final Map releaseRelationMap = CommonUtils.isNullOrEmptyMap(project.getReleaseIdToUsage()) ? new HashMap<>() : project.getReleaseIdToUsage(); + Set projectPkgIds = CommonUtils.isNullOrEmptyCollection(project.getPackageIds()) ? new HashSet<>() : project.getPackageIds(); countMap.put(REL_CREATION_COUNT_KEY, 0); countMap.put(REL_REUSE_COUNT_KEY, 0); countMap.put(PKG_CREATION_COUNT_KEY, 0); countMap.put(PKG_REUSE_COUNT_KEY, 0); int relCreationCount = 0, relReuseCount = 0, pkgCreationCount = 0, pkgReuseCount = 0; - for (Map.Entry> entry : vcsToComponentMap.entrySet()) { Component comp = createComponent(entry.getKey()); Release release = new Release(); @@ -595,6 +587,7 @@ private Map importAllComponentsAsPackages(Map importAllComponentsAsPackages(Map packagesToBeRemoved = new ArrayList<>(); + for(String pkgId: projectPkgIds){ + Package existingPkg = packageDatabaseHandler.getPackageById(pkgId); + if(pkg.getName().equals(existingPkg.getName()) && !pkg.getVersion().equals(existingPkg.getVersion())){ + log.error("existing pkg and release need to be replaced " + existingPkg.getId() ); + packagesToBeRemoved.add(pkgId); + } + } + unlinkPackageAndReleaseFormProject(project, packagesToBeRemoved); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(pkgAddSummary.getId())) { pkg.setId(pkgAddSummary.getId()); if (AddDocumentRequestStatus.DUPLICATE.equals(pkgAddSummary.getRequestStatus())) { @@ -974,4 +979,34 @@ public String getComponetNameById(String id, User user) throws SW360Exception { Component comp = componentDatabaseHandler.getComponent(id, user); return comp.getName(); } + + public void unlinkPackageAndReleaseFormProject(Project project, List packagesToBeRemoved) throws SW360Exception { + Map releaseRelationMap = CommonUtils.isNullOrEmptyMap(project.getReleaseIdToUsage()) ? new HashMap<>() : project.getReleaseIdToUsage(); + Set projectPkgIds = CommonUtils.isNullOrEmptyCollection(project.getPackageIds()) ? new HashSet<>() : project.getPackageIds(); + + for (String pkgIdToBeRemoved: packagesToBeRemoved){ + //fetching the linked release form a pkg + String linkedReleaseId = packageDatabaseHandler.getPackageById(pkgIdToBeRemoved).getReleaseId(); + + //Removing the pkg + projectPkgIds.remove(pkgIdToBeRemoved); + + //removing the release linked from pkg only if the other packages in project are not linked to this rel + boolean isReleaseUnlinkPossible = true; + for(String pkgId: projectPkgIds){ + Package pkg = packageDatabaseHandler.getPackageById(pkgId); + if(CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId()) && pkg.getReleaseId().equals(linkedReleaseId)){ + isReleaseUnlinkPossible = false; + break; + } + } + + if(isReleaseUnlinkPossible){ + releaseRelationMap.remove(linkedReleaseId); + } + } + + project.setPackageIds(projectPkgIds); + project.setReleaseIdToUsage(releaseRelationMap); + } } diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java index 11e0b7d648..ed49556088 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java @@ -827,7 +827,7 @@ private boolean isLinkedReleasesUpdateFromLinkedPackagesFailed(Project updatedPr */ for (Map.Entry> entry : releaseIdToPackageIdsMap.entrySet()) { if (targetMap.containsKey(entry.getKey()) && - CommonUtils.isNotEmpty(CommonUtils.nullToEmptySet(Sets.intersection(entry.getValue(), unlinkedPacakgeIds)))) { + !CommonUtils.isNotEmpty(CommonUtils.nullToEmptySet(Sets.intersection(entry.getValue(), unlinkedPacakgeIds)))) { targetMap.remove(entry.getKey()); } } @@ -1843,7 +1843,7 @@ public RequestSummary importBomFromAttachmentContent(User user, String attachmen } } - public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId) throws SW360Exception { + public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId, boolean replacePackageFlag) throws SW360Exception { final AttachmentContent attachmentContent = attachmentConnector.getAttachmentContent(attachmentContentId); final Duration timeout = Duration.durationOf(30, TimeUnit.SECONDS); try { @@ -1852,7 +1852,7 @@ public RequestSummary importCycloneDxFromAttachmentContent(User user, String att .unsafeGetAttachmentStream(attachmentContent)) { final CycloneDxBOMImporter cycloneDxBOMImporter = new CycloneDxBOMImporter(this, componentDatabaseHandler, packageDatabaseHandler, attachmentConnector, user); - return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user); + return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user, replacePackageFlag); } } catch (IOException e) { log.error("Error while importing / parsing CycloneDX SBOM! ", e); diff --git a/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java b/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java index fdfdb391ba..63fc321f5e 100644 --- a/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java +++ b/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java @@ -269,10 +269,10 @@ public RequestSummary importBomFromAttachmentContent(User user, String attachmen } @Override - public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId) throws SW360Exception { + public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId, boolean replacePackageFlag) throws SW360Exception { assertId(attachmentContentId); assertUser(user); - return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId); + return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId, replacePackageFlag); } @Override diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java index 852d1bdc65..82fb5c3ce5 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java @@ -457,6 +457,7 @@ private void importBom(ResourceRequest request, ResourceResponse response) { String bomtype = parameters.getValue(BOM_TYPE); String projectId = parameters.getValue(PROJECT_ID); final RequestSummary requestSummary; + final boolean replacePackageFlag = true; try { boolean isSPDX = "SPDX".equals(bomtype); @@ -465,7 +466,7 @@ private void importBom(ResourceRequest request, ResourceResponse response) { requestSummary = projectClient.importBomFromAttachmentContent(user, attachmentContentId); } else { Locale locale = request.getLocale(); - requestSummary = projectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId); + requestSummary = projectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId, replacePackageFlag); } boolean isSuccess = RequestStatus.SUCCESS.equals(requestSummary.getRequestStatus()); diff --git a/libraries/datahandler/src/main/thrift/projects.thrift b/libraries/datahandler/src/main/thrift/projects.thrift index f94ef2d980..e5fa010f05 100644 --- a/libraries/datahandler/src/main/thrift/projects.thrift +++ b/libraries/datahandler/src/main/thrift/projects.thrift @@ -608,7 +608,7 @@ service ProjectService { /** * Parse a CycloneDx SBoM file (XML or JSON) and write the information to SW360 as Project / Component / Release / Package */ - RequestSummary importCycloneDxFromAttachmentContent(1: User user, 2: string attachmentContentId, 3: string projectId) throws (1: SW360Exception exp); + RequestSummary importCycloneDxFromAttachmentContent(1: User user, 2: string attachmentContentId, 3: string projectId, 4: bool replacePackageFlag) throws (1: SW360Exception exp); /** * Export a CycloneDx SBoM file (XML or JSON) for a Project 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 1569c06b12..b62627a6d5 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 @@ -1767,7 +1767,7 @@ public ResponseEntity importSBOM( } projectId = requestSummary.getMessage(); } else { - requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), ""); + requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), "", false); if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); @@ -1821,14 +1821,14 @@ public ResponseEntity importSBOMonProject( @Parameter(description = "Project ID", example = "376576") @PathVariable(value = "id", required = true) String id, @Parameter(description = "SBOM file") - @RequestBody MultipartFile file + @RequestBody MultipartFile file, + @RequestParam(value = "replacePackageFlag", required = true) boolean replacePackageFlag ) throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Attachment attachment = null; final RequestSummary requestSummary; String projectId = null; Map messageMap = new HashMap<>(); - try { attachment = attachmentService.uploadAttachment(file, new Attachment(), sw360User); } catch (IOException e) { @@ -1836,7 +1836,7 @@ public ResponseEntity importSBOMonProject( throw new RuntimeException("failed to upload attachment", e); } - requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id); + requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id, replacePackageFlag); if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); 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 2f5f55b511..76fee85dee 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 @@ -857,9 +857,9 @@ public RequestSummary importSPDX(User user, String attachmentContentId) throws T * @return RequestSummary * @throws TException */ - public RequestSummary importCycloneDX(User user, String attachmentContentId, String projectId) throws TException { + public RequestSummary importCycloneDX(User user, String attachmentContentId, String projectId, boolean replacePackageFlag) throws TException { ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); - return sw360ProjectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, CommonUtils.nullToEmptyString(projectId)); + return sw360ProjectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, CommonUtils.nullToEmptyString(projectId), replacePackageFlag); } /** 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 6418008b67..391f2fde7a 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 @@ -538,7 +538,7 @@ public void before() throws TException, IOException { requestSummaryForCycloneDX.setMessage("{\"projectId\":\"" + cycloneDXProject.getId() + "\"}"); given(this.projectServiceMock.importSPDX(any(),any())).willReturn(requestSummaryForSPDX); - given(this.projectServiceMock.importCycloneDX(any(),any(),any())).willReturn(requestSummaryForCycloneDX); + given(this.projectServiceMock.importCycloneDX(any(),any(),any(),anyBoolean())).willReturn(requestSummaryForCycloneDX); given(this.sw360ReportServiceMock.getProjectBuffer(any(),anyBoolean(),any())).willReturn(ByteBuffer.allocate(10000)); given(this.projectServiceMock.getProjectsForUser(any(), any())).willReturn(projectList); given(this.projectServiceMock.getProjectForUserById(eq(project.getId()), any())).willReturn(project); @@ -2314,6 +2314,7 @@ public void should_document_import_cyclonedx_on_project() throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/api/projects/"+project.getId()+"/import/SBOM") .content(file.getBytes()) .contentType(MediaType.MULTIPART_FORM_DATA) + .queryParam("replacePackageFlag", "true") .header("Authorization", "Bearer " + accessToken); this.mockMvc.perform(builder).andExpect(status().isOk()).andDo(this.documentationHandler.document()); }