From 6e7b6eb30bd362d41aa385f92b88525fb942c210 Mon Sep 17 00:00:00 2001 From: Nikesh kumar Date: Thu, 20 Jun 2024 20:40:00 +0530 Subject: [PATCH] fix(rest): Added code for for updating multiple project attachments. Signed-off-by: Nikesh kumar --- .../project/ProjectController.java | 30 ++-- .../project/Sw360ProjectService.java | 133 ++++++++++++++++++ .../restdocs/ProjectSpecTest.java | 1 + .../restdocs/TestRestDocsSpecBase.java | 3 +- 4 files changed, 153 insertions(+), 14 deletions(-) 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 663248de2f..c179138ded 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 @@ -1450,28 +1450,32 @@ public ResponseEntity addAttachmentToProject( @Parameter(description = "Project ID.") @PathVariable("projectId") String projectId, @Parameter(description = "File to attach") - @RequestPart("file") MultipartFile file, + @RequestPart("file") MultipartFile[] files, @Parameter(description = "Attachment description") - @RequestPart("attachment") Attachment newAttachment - ) throws TException { + @RequestPart("attachment") Attachment newAttachment, + HttpServletRequest request, + HttpServletResponse response + ) throws TException,IOException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); final Project project = projectService.getProjectForUserById(projectId, sw360User); - Attachment attachment = null; - try { - attachment = attachmentService.uploadAttachment(file, newAttachment, sw360User); - } catch (IOException e) { - log.error("failed to upload attachment", e); - throw new RuntimeException("failed to upload attachment", e); + + + for (MultipartFile file : files) { + try { + Attachment attachment = attachmentService.uploadAttachment(file, newAttachment, sw360User); + project.addToAttachments(attachment); + } catch (IOException e) { + log.error("Failed to upload attachment", e); + throw new RuntimeException("Failed to upload attachment", e); + } } - project.addToAttachments(attachment); - RequestStatus updateProjectStatus = projectService.updateProject(project, sw360User); - HttpStatus status = HttpStatus.OK; + RequestStatus updateProjectStatus = projectService.updateProjectForAttachment(project, sw360User, request, response, projectId); HalResource halResource = createHalProject(project, sw360User); if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); } - return new ResponseEntity<>(halResource, status); + return new ResponseEntity<>(halResource, HttpStatus.OK); } @Operation( 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 547abc8e89..ec526b9b25 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 @@ -21,6 +21,7 @@ import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.common.SW360Constants; import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.common.ThriftEnumUtils; import org.eclipse.sw360.datahandler.common.WrappedException.WrappedTException; import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; @@ -55,6 +56,7 @@ import org.eclipse.sw360.datahandler.thrift.projects.ProjectLink; import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.projects.ProjectProjectRelationship; +import org.eclipse.sw360.datahandler.thrift.projects.ProjectRelationship; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer; import org.eclipse.sw360.rest.resourceserver.core.AwareOfRestServices; @@ -62,6 +64,7 @@ import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; import org.eclipse.sw360.rest.resourceserver.release.ReleaseController; import org.eclipse.sw360.rest.resourceserver.release.Sw360ReleaseService; +import org.jose4j.json.internal.json_simple.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; @@ -76,9 +79,14 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + import javax.annotation.PreDestroy; +import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -905,5 +913,130 @@ public Integer loadPreferredClearingDateLimit() { // returning default value 7 (days) if variable is not set return limit < 1 ? 7 : limit; } + + public RequestStatus updateProjectForAttachment(Project project, User sw360User, HttpServletRequest request, + HttpServletResponse response, String projectId) throws TException, IOException { + ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); + String cyclicLinkedProjectPath = null; + rch.checkForCyclicOrInvalidDependencies(sw360ProjectClient, project, sw360User); + + verifyIfAttachmentsExist(request, response, projectId, sw360User, project); + // TODO: Move this logic to backend + if (project.getReleaseIdToUsage() != null) { + for (String releaseId : project.getReleaseIdToUsage().keySet()) { + if (isNullEmptyOrWhitespace(releaseId)) { + throw new HttpMessageNotReadableException("Release Id can't be empty"); + } + } + } + + if (project.getVendor() != null && project.getVendorId() == null) { + project.setVendorId(project.getVendor().getId()); + } + + RequestStatus requestStatus; + if (Sw360ResourceServer.IS_FORCE_UPDATE_ENABLED) { + requestStatus = sw360ProjectClient.updateProjectWithForceFlag(project, sw360User, true); + } else { + requestStatus = sw360ProjectClient.updateProject(project, sw360User); + System.out.println("********** requestStatus *********" + requestStatus); + } + if (requestStatus == RequestStatus.NAMINGERROR) { + throw new HttpMessageNotReadableException( + "Project name field cannot be empty or contain only whitespace character"); + } + + if (requestStatus == RequestStatus.CLOSED_UPDATE_NOT_ALLOWED) { + throw new RuntimeException("User cannot modify a closed project"); + } + if (requestStatus == RequestStatus.INVALID_INPUT) { + throw new HttpMessageNotReadableException("Dependent document Id/ids not valid."); + } else if (requestStatus != RequestStatus.SENT_TO_MODERATOR && requestStatus != RequestStatus.SUCCESS) { + throw new RuntimeException("sw360 project with name '" + project.getName() + " cannot be updated."); + } + return requestStatus; + } + + private void verifyIfAttachmentsExist(HttpServletRequest request, HttpServletResponse response, String projectId, User user, Project project) + throws TException, IOException { + + Set attachments = project.getAttachments(); + Map filenameToAttachmentId = attachments.stream() + .collect(Collectors.toMap(Attachment::getAttachmentContentId, Attachment::getFilename)); + + ThriftClients thriftClients = new ThriftClients(); + AttachmentService.Iface attachmentClient = thriftClients.makeAttachmentClient(); + JSONObject jsonObject = new JSONObject(); + Set missingAttachmentFilenames = new HashSet<>(); + + String selectedProjectRelationsParam = request.getParameter("selectedProjectRelations"); + List selectedProjectRelations = selectedProjectRelationsParam == null ? Collections.emptyList() + : Arrays.asList(selectedProjectRelationsParam.split(",")); + + Set selectedProjectRelationships = selectedProjectRelations.stream() + .map(rel -> ThriftEnumUtils.stringToEnum(rel, ProjectRelationship.class)).filter(Objects::nonNull) + .collect(Collectors.toSet()); + + Set selectedAttachmentIdsWithPath = filterAttachmentSelectionOnProjectRelation( + selectedProjectRelationships, attachments, user, project); + + Set selectedAttachmentIds = selectedAttachmentIdsWithPath.stream() + .map(fullpath -> fullpath.split(":")[fullpath.split(":").length - 1]).collect(Collectors.toSet()); + + for (String attachmentId : selectedAttachmentIds) { + try { + attachmentClient.getAttachmentContent(attachmentId); + } catch (SW360Exception sw360Exp) { + if (sw360Exp.getErrorCode() == 404) { + missingAttachmentFilenames.add(filenameToAttachmentId.get(attachmentId)); + log.error("Attachment not found: {}", attachmentId, sw360Exp); + } else { + throw sw360Exp; + } + } catch (TException exception) { + log.error("Error getting attachment: {}", attachmentId, exception); + throw exception; + } + } + + if (!missingAttachmentFilenames.isEmpty()) { + jsonObject.put("missingAttachments", missingAttachmentFilenames); + } + + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(jsonObject.toString()); + response.getWriter().flush(); + } + + private Set filterAttachmentSelectionOnProjectRelation( + Set listOfSelectedProjectRelationships, Set selectedAttachments, User user, + Project project) { + List filteredMappedProjectLinks = createLinkedProjects(project, + link -> filterAndSortAttachments(SW360Constants.LICENSE_INFO_ATTACHMENT_TYPES).apply(link), true, user); + Set filteredProjectIds = filteredProjectIds(filteredMappedProjectLinks); + Set selectedAttachmentIdsWithPath = new HashSet<>(); + + if (selectedAttachments != null) { + selectedAttachmentIdsWithPath = selectedAttachments.stream().map(Attachment::getAttachmentContentId) + .collect(Collectors.toSet()); + } + selectedAttachmentIdsWithPath = selectedAttachmentIdsWithPath.stream().filter(fullPath -> { + String[] pathParts = fullPath.split(":"); + int length = pathParts.length; + if (length >= 5) { + String projectIdOpted = pathParts[pathParts.length - 5]; + return filteredProjectIds.contains(projectIdOpted); + } + return true; + }).collect(Collectors.toSet()); + + return selectedAttachmentIdsWithPath; + } + + private Set filteredProjectIds(List filteredProjectLinks) { + return filteredProjectLinks.stream().map(ProjectLink::getId).collect(Collectors.toSet()); + } + } 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 0cb741fbc8..18789f03fb 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 @@ -581,6 +581,7 @@ public void before() throws TException, IOException { given(this.projectServiceMock.deleteProject(eq(project.getId()), any())).willReturn(RequestStatus.SUCCESS); given(this.projectServiceMock.updateProjectReleaseRelationship(any(), any(), any())).willReturn(projectReleaseRelationshipResponseBody); given(this.projectServiceMock.getClearingInfo(eq(project), any())).willReturn(project); + given(this.projectServiceMock.updateProjectForAttachment(eq(project7), any(), any(), any(), eq(projectName))).willReturn(RequestStatus.SUCCESS); given(this.projectServiceMock.getCyclicLinkedProjectPath(eq(project7), any())).willReturn(""); given(this.projectServiceMock.updateProject(eq(project7), any())).willReturn(RequestStatus.SUCCESS); given(this.projectServiceMock.convertToEmbeddedWithExternalIds(eq(project))).willReturn( diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/TestRestDocsSpecBase.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/TestRestDocsSpecBase.java index 584c5cb7aa..1c910d0c82 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/TestRestDocsSpecBase.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/TestRestDocsSpecBase.java @@ -110,9 +110,10 @@ public void testAttachmentUpload(String url, String id) throws Exception { */ MockMultipartFile jsonFile = new MockMultipartFile("attachment", "", "application/json", new ByteArrayInputStream(attachment.getBytes())); + MockMultipartFile[] files = new MockMultipartFile[]{jsonFile}; MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart(url + id + "/attachments") .file("file", "@/spring-core-4.3.4.RELEASE.jar".getBytes()) - .file(jsonFile) + .file(files[0]) .contentType(MediaType.MULTIPART_FORM_DATA) .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)); this.mockMvc.perform(builder).andExpect(status().isOk()).andDo(this.documentationHandler.document());